| 1 | |
| 2 | |
| 3 |
|
| 4 |
|
| 5 |
|
| 6 | import os
|
| 7 |
|
| 8 | from lib.py import ksft_run, ksft_exit, ksft_eq, ksft_raises
|
| 9 | from lib.py import ksft_variants, KsftNamedVariant
|
| 10 | from lib.py import CmdExitFailure, KsftSkipEx
|
| 11 | from lib.py import NetDrvEpEnv
|
| 12 | from lib.py import cmd, ip, defer, ethtool
|
| 13 |
|
| 14 | MACSEC_KEY = "12345678901234567890123456789012"
|
| 15 | MACSEC_VLAN_VID = 10
|
| 16 |
|
| 17 | |
| 18 | |
| 19 | MACSEC_PFX = f"ms{os.getpid()}_"
|
| 20 |
|
| 21 |
|
| 22 | def _macsec_name(idx=0):
|
| 23 | return f"{MACSEC_PFX}{idx}"
|
| 24 |
|
| 25 |
|
| 26 | def _get_macsec_offload(dev):
|
| 27 |
|
| 28 | info = ip(f"-d link show dev {dev}", json=True)[0]
|
| 29 | return info.get("linkinfo", {}).get("info_data", {}).get("offload")
|
| 30 |
|
| 31 |
|
| 32 | def _get_features(dev):
|
| 33 |
|
| 34 | return ethtool(f"-k {dev}", json=True)[0]
|
| 35 |
|
| 36 |
|
| 37 | def _require_ip_macsec(cfg):
|
| 38 |
|
| 39 | for host in [None, cfg.remote]:
|
| 40 | out = cmd("ip macsec help", fail=False, host=host)
|
| 41 | if "Usage" not in out.stdout + out.stderr:
|
| 42 | where = "remote" if host else "local"
|
| 43 | raise KsftSkipEx(f"iproute2 too old on {where},"
|
| 44 | " missing macsec support")
|
| 45 |
|
| 46 |
|
| 47 | def _require_ip_macsec_offload():
|
| 48 |
|
| 49 | out = cmd("ip macsec help", fail=False)
|
| 50 | if "offload" not in out.stdout + out.stderr:
|
| 51 | raise KsftSkipEx("iproute2 too old, missing macsec offload")
|
| 52 |
|
| 53 |
|
| 54 | def _require_macsec_offload(cfg):
|
| 55 |
|
| 56 | _require_ip_macsec_offload()
|
| 57 | try:
|
| 58 | feat = ethtool(f"-k {cfg.ifname}", json=True)[0]
|
| 59 | except (CmdExitFailure, IndexError) as e:
|
| 60 | raise KsftSkipEx(
|
| 61 | f"can't query features: {e}") from e
|
| 62 | if not feat.get("macsec-hw-offload", {}).get("active"):
|
| 63 | raise KsftSkipEx("macsec-hw-offload not supported")
|
| 64 |
|
| 65 |
|
| 66 | def _get_mac(ifname, host=None):
|
| 67 |
|
| 68 | dev = ip(f"link show dev {ifname}", json=True, host=host)
|
| 69 | return dev[0]["address"]
|
| 70 |
|
| 71 |
|
| 72 | def _setup_macsec_sa(cfg, name):
|
| 73 |
|
| 74 | local_mac = _get_mac(name)
|
| 75 | remote_mac = _get_mac(name, host=cfg.remote)
|
| 76 |
|
| 77 | ip(f"macsec add {name} tx sa 0 pn 1 on key 01 {MACSEC_KEY}")
|
| 78 | ip(f"macsec add {name} rx port 1 address {remote_mac}")
|
| 79 | ip(f"macsec add {name} rx port 1 address {remote_mac} "
|
| 80 | f"sa 0 pn 1 on key 02 {MACSEC_KEY}")
|
| 81 |
|
| 82 | ip(f"macsec add {name} tx sa 0 pn 1 on key 02 {MACSEC_KEY}",
|
| 83 | host=cfg.remote)
|
| 84 | ip(f"macsec add {name} rx port 1 address {local_mac}", host=cfg.remote)
|
| 85 | ip(f"macsec add {name} rx port 1 address {local_mac} "
|
| 86 | f"sa 0 pn 1 on key 01 {MACSEC_KEY}", host=cfg.remote)
|
| 87 |
|
| 88 |
|
| 89 | def _setup_macsec_devs(cfg, name, offload):
|
| 90 | |
| 91 |
|
| 92 | Only the local device gets HW offload; the remote always uses software
|
| 93 | MACsec since it may not support offload at all.
|
| 94 |
|
| 95 | offload_arg = "mac" if offload else "off"
|
| 96 |
|
| 97 | ip(f"link add link {cfg.ifname} {name} "
|
| 98 | f"type macsec encrypt on offload {offload_arg}")
|
| 99 | defer(ip, f"link del {name}")
|
| 100 | ip(f"link add link {cfg.remote_ifname} {name} "
|
| 101 | f"type macsec encrypt on", host=cfg.remote)
|
| 102 | defer(ip, f"link del {name}", host=cfg.remote)
|
| 103 |
|
| 104 |
|
| 105 | def _set_offload(name, offload):
|
| 106 |
|
| 107 | offload_arg = "mac" if offload else "off"
|
| 108 |
|
| 109 | ip(f"link set {name} type macsec encrypt on offload {offload_arg}")
|
| 110 |
|
| 111 |
|
| 112 | def _setup_vlans(cfg, name, vid):
|
| 113 |
|
| 114 | vlan_name = f"{name}.{vid}"
|
| 115 |
|
| 116 | ip(f"link add link {name} {vlan_name} type vlan id {vid}")
|
| 117 | defer(ip, f"link del {vlan_name}")
|
| 118 | ip(f"link add link {name} {vlan_name} type vlan id {vid}", host=cfg.remote)
|
| 119 | defer(ip, f"link del {vlan_name}", host=cfg.remote)
|
| 120 |
|
| 121 |
|
| 122 | def _setup_vlan_ips(cfg, name, vid):
|
| 123 |
|
| 124 | local_ip = "198.51.100.1"
|
| 125 | remote_ip = "198.51.100.2"
|
| 126 | vlan_name = f"{name}.{vid}"
|
| 127 |
|
| 128 | ip(f"addr add {local_ip}/24 dev {vlan_name}")
|
| 129 | ip(f"addr add {remote_ip}/24 dev {vlan_name}", host=cfg.remote)
|
| 130 | ip(f"link set {name} up")
|
| 131 | ip(f"link set {name} up", host=cfg.remote)
|
| 132 | ip(f"link set {vlan_name} up")
|
| 133 | ip(f"link set {vlan_name} up", host=cfg.remote)
|
| 134 |
|
| 135 | return vlan_name, remote_ip
|
| 136 |
|
| 137 |
|
| 138 | def test_offload_api(cfg) -> None:
|
| 139 |
|
| 140 |
|
| 141 | _require_macsec_offload(cfg)
|
| 142 | ms0 = _macsec_name(0)
|
| 143 | ms1 = _macsec_name(1)
|
| 144 | ms2 = _macsec_name(2)
|
| 145 |
|
| 146 | |
| 147 | ip(f"link add link {cfg.ifname} {ms0} type macsec "
|
| 148 | f"port 4 encrypt on offload mac")
|
| 149 | defer(ip, f"link del {ms0}")
|
| 150 |
|
| 151 | ip(f"link add link {cfg.ifname} {ms1} type macsec "
|
| 152 | f"address aa:bb:cc:dd:ee:ff port 5 encrypt on offload mac")
|
| 153 | defer(ip, f"link del {ms1}")
|
| 154 |
|
| 155 | ip(f"link add link {cfg.ifname} {ms2} type macsec "
|
| 156 | f"sci abbacdde01020304 encrypt on offload mac")
|
| 157 | defer(ip, f"link del {ms2}")
|
| 158 |
|
| 159 | |
| 160 | ip(f"macsec add {ms0} tx sa 0 pn 1024 on "
|
| 161 | "key 01 12345678901234567890123456789012")
|
| 162 |
|
| 163 | |
| 164 | ip(f"macsec add {ms0} rx port 1234 address 1c:ed:de:ad:be:ef")
|
| 165 | ip(f"macsec add {ms0} rx port 1234 address 1c:ed:de:ad:be:ef "
|
| 166 | "sa 0 pn 1 on key 00 0123456789abcdef0123456789abcdef")
|
| 167 |
|
| 168 | |
| 169 | with ksft_raises(CmdExitFailure):
|
| 170 | ip(f"link set {ms0} type macsec offload off")
|
| 171 | with ksft_raises(CmdExitFailure):
|
| 172 | ip(f"macsec offload {ms0} off")
|
| 173 |
|
| 174 | |
| 175 | ip(f"link set {ms2} type macsec offload off")
|
| 176 | ip(f"link set {ms2} type macsec encrypt on offload mac")
|
| 177 |
|
| 178 | |
| 179 | ip(f"macsec offload {ms2} off")
|
| 180 | ip(f"macsec offload {ms2} mac")
|
| 181 |
|
| 182 |
|
| 183 | def test_max_secy(cfg) -> None:
|
| 184 |
|
| 185 |
|
| 186 | cfg.require_nsim()
|
| 187 | _require_ip_macsec_offload()
|
| 188 | ms0 = _macsec_name(0)
|
| 189 | ms1 = _macsec_name(1)
|
| 190 | ms2 = _macsec_name(2)
|
| 191 | ms3 = _macsec_name(3)
|
| 192 |
|
| 193 | ip(f"link add link {cfg.ifname} {ms0} type macsec "
|
| 194 | f"port 4 encrypt on offload mac")
|
| 195 | defer(ip, f"link del {ms0}")
|
| 196 |
|
| 197 | ip(f"link add link {cfg.ifname} {ms1} type macsec "
|
| 198 | f"address aa:bb:cc:dd:ee:ff port 5 encrypt on offload mac")
|
| 199 | defer(ip, f"link del {ms1}")
|
| 200 |
|
| 201 | ip(f"link add link {cfg.ifname} {ms2} type macsec "
|
| 202 | f"sci abbacdde01020304 encrypt on offload mac")
|
| 203 | defer(ip, f"link del {ms2}")
|
| 204 | with ksft_raises(CmdExitFailure):
|
| 205 | ip(f"link add link {cfg.ifname} {ms3} "
|
| 206 | f"type macsec port 8 encrypt on offload mac")
|
| 207 |
|
| 208 |
|
| 209 | def test_max_sc(cfg) -> None:
|
| 210 |
|
| 211 |
|
| 212 | cfg.require_nsim()
|
| 213 | _require_ip_macsec_offload()
|
| 214 | ms0 = _macsec_name(0)
|
| 215 |
|
| 216 | ip(f"link add link {cfg.ifname} {ms0} type macsec "
|
| 217 | f"port 4 encrypt on offload mac")
|
| 218 | defer(ip, f"link del {ms0}")
|
| 219 | ip(f"macsec add {ms0} rx port 1234 address 1c:ed:de:ad:be:ef")
|
| 220 | with ksft_raises(CmdExitFailure):
|
| 221 | ip(f"macsec add {ms0} rx port 1235 address 1c:ed:de:ad:be:ef")
|
| 222 |
|
| 223 |
|
| 224 | def test_offload_state(cfg) -> None:
|
| 225 |
|
| 226 |
|
| 227 | _require_macsec_offload(cfg)
|
| 228 | ms0 = _macsec_name(0)
|
| 229 |
|
| 230 | |
| 231 | ip(f"link add link {cfg.ifname} {ms0} type macsec "
|
| 232 | f"encrypt on offload mac")
|
| 233 | cleanup = defer(ip, f"link del {ms0}")
|
| 234 |
|
| 235 | ksft_eq(_get_macsec_offload(ms0), "mac",
|
| 236 | "created with offload: should be mac")
|
| 237 | feats_on_1 = _get_features(ms0)
|
| 238 |
|
| 239 | ip(f"link set {ms0} type macsec offload off")
|
| 240 | ksft_eq(_get_macsec_offload(ms0), "off",
|
| 241 | "offload disabled: should be off")
|
| 242 | feats_off_1 = _get_features(ms0)
|
| 243 |
|
| 244 | ip(f"link set {ms0} type macsec encrypt on offload mac")
|
| 245 | ksft_eq(_get_macsec_offload(ms0), "mac",
|
| 246 | "offload re-enabled: should be mac")
|
| 247 | ksft_eq(_get_features(ms0), feats_on_1,
|
| 248 | "features should match first offload-on snapshot")
|
| 249 |
|
| 250 | |
| 251 | cleanup.exec()
|
| 252 | ip(f"link add link {cfg.ifname} {ms0} type macsec")
|
| 253 | defer(ip, f"link del {ms0}")
|
| 254 | ksft_eq(_get_macsec_offload(ms0), "off",
|
| 255 | "created without offload: should be off")
|
| 256 | ksft_eq(_get_features(ms0), feats_off_1,
|
| 257 | "features should match first offload-off snapshot")
|
| 258 |
|
| 259 | ip(f"link set {ms0} type macsec encrypt on offload mac")
|
| 260 | ksft_eq(_get_macsec_offload(ms0), "mac",
|
| 261 | "offload enabled after create: should be mac")
|
| 262 | ksft_eq(_get_features(ms0), feats_on_1,
|
| 263 | "features should match first offload-on snapshot")
|
| 264 |
|
| 265 |
|
| 266 | def _check_nsim_vid(cfg, vid, expected) -> None:
|
| 267 |
|
| 268 |
|
| 269 | nsim = cfg.get_local_nsim_dev()
|
| 270 | if not nsim:
|
| 271 | return
|
| 272 |
|
| 273 | vlan_path = os.path.join(nsim.nsims[0].dfs_dir, "vlan")
|
| 274 | with open(vlan_path, encoding="utf-8") as f:
|
| 275 | vids = f.read()
|
| 276 | found = f"ctag {vid}\n" in vids
|
| 277 | ksft_eq(found, expected,
|
| 278 | f"VLAN {vid} {'expected' if expected else 'not expected'}"
|
| 279 | f" in debugfs")
|
| 280 |
|
| 281 |
|
| 282 | @ksft_variants([
|
| 283 | KsftNamedVariant("offloaded", True),
|
| 284 | KsftNamedVariant("software", False),
|
| 285 | ])
|
| 286 | def test_vlan(cfg, offload) -> None:
|
| 287 |
|
| 288 |
|
| 289 | _require_ip_macsec(cfg)
|
| 290 | if offload:
|
| 291 | _require_macsec_offload(cfg)
|
| 292 | else:
|
| 293 | _require_ip_macsec_offload()
|
| 294 | name = _macsec_name()
|
| 295 | _setup_macsec_devs(cfg, name, offload=offload)
|
| 296 | _setup_macsec_sa(cfg, name)
|
| 297 | _setup_vlans(cfg, name, MACSEC_VLAN_VID)
|
| 298 | vlan_name, remote_ip = _setup_vlan_ips(cfg, name, MACSEC_VLAN_VID)
|
| 299 | _check_nsim_vid(cfg, MACSEC_VLAN_VID, offload)
|
| 300 | |
| 301 | |
| 302 | if not offload or not cfg.get_local_nsim_dev():
|
| 303 | cmd(f"ping -I {vlan_name} -c 1 -W 5 {remote_ip}")
|
| 304 |
|
| 305 |
|
| 306 | @ksft_variants([
|
| 307 | KsftNamedVariant("on_to_off", True),
|
| 308 | KsftNamedVariant("off_to_on", False),
|
| 309 | ])
|
| 310 | def test_vlan_toggle(cfg, offload) -> None:
|
| 311 |
|
| 312 |
|
| 313 | _require_ip_macsec(cfg)
|
| 314 | _require_macsec_offload(cfg)
|
| 315 | name = _macsec_name()
|
| 316 | _setup_macsec_devs(cfg, name, offload=offload)
|
| 317 | _setup_vlans(cfg, name, MACSEC_VLAN_VID)
|
| 318 | _check_nsim_vid(cfg, MACSEC_VLAN_VID, offload)
|
| 319 | _set_offload(name, offload=not offload)
|
| 320 | _check_nsim_vid(cfg, MACSEC_VLAN_VID, not offload)
|
| 321 | vlan_name, remote_ip = _setup_vlan_ips(cfg, name, MACSEC_VLAN_VID)
|
| 322 | _setup_macsec_sa(cfg, name)
|
| 323 | |
| 324 | |
| 325 | if offload or not cfg.get_local_nsim_dev():
|
| 326 | cmd(f"ping -I {vlan_name} -c 1 -W 5 {remote_ip}")
|
| 327 |
|
| 328 |
|
| 329 | def main() -> None:
|
| 330 |
|
| 331 | with NetDrvEpEnv(__file__) as cfg:
|
| 332 | ksft_run([test_offload_api,
|
| 333 | test_max_secy,
|
| 334 | test_max_sc,
|
| 335 | test_offload_state,
|
| 336 | test_vlan,
|
| 337 | test_vlan_toggle,
|
| 338 | ], args=(cfg,))
|
| 339 | ksft_exit()
|
| 340 |
|
| 341 |
|
| 342 | if __name__ == "__main__":
|
| 343 | main()
|