|
| 1 | +# Copyright (c) 2021 Microsoft Open Technologies, Inc. |
| 2 | +# |
| 3 | +# Licensed under the Apache License, Version 2.0 (the "License"); you may |
| 4 | +# not use this file except in compliance with the License. You may obtain |
| 5 | +# a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 |
| 6 | +# |
| 7 | +# THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR |
| 8 | +# CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT |
| 9 | +# LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS |
| 10 | +# FOR A PARTICULAR PURPOSE, MERCHANTABILITY OR NON-INFRINGEMENT. |
| 11 | +# |
| 12 | +# See the Apache Version 2.0 License for specific language governing |
| 13 | +# permissions and limitations under the License. |
| 14 | +# |
| 15 | +# Microsoft would like to thank the following companies for their review and |
| 16 | +# assistance with these files: Intel Corporation, Mellanox Technologies Ltd, |
| 17 | +# Dell Products, L.P., Facebook, Inc., Marvell International Ltd. |
| 18 | +# |
| 19 | +# |
| 20 | + |
| 21 | +# Optional SONiC control-plane simulation for standalone sai_test benches. |
| 22 | +# |
| 23 | +# In a real SONiC device teamd creates PortChannel netdevs and IntfMgr assigns the |
| 24 | +# interface IPs that the SAI backend mirrors onto LAG/SVI router interfaces. A |
| 25 | +# standalone sai_test bench (no teamd / IntfMgr) has neither, so LAG bring-up and |
| 26 | +# routed-to-LAG/SVI forwarding can fail. When SIMULATE_SONIC=1 the config helpers |
| 27 | +# call into this module to emulate that setup from the test's setUp path. |
| 28 | +# |
| 29 | +# This is opt-in and default-preserving: with SIMULATE_SONIC unset (the default) all |
| 30 | +# entry points are no-ops, so real ASICs and other harnesses are unaffected. All |
| 31 | +# interface names and address patterns are environment-overridable so the defaults |
| 32 | +# can be retargeted without code changes. This module is backend-neutral: it uses |
| 33 | +# standard Linux "ip" for PortChannel netdevs and LAG router-interface IPs, and for |
| 34 | +# a VLAN (SVI) router interface -- which has no host-interface netdev to mirror from |
| 35 | +# -- it runs the command templates the harness provides via SVI_RIF_SET_IP_CMD (and |
| 36 | +# optional SVI_RIF_PROBE_CMD), so no backend-specific tooling is hardcoded here. |
| 37 | + |
| 38 | +import os |
| 39 | +import shlex |
| 40 | +import subprocess |
| 41 | +import time |
| 42 | + |
| 43 | + |
| 44 | +def enabled(): |
| 45 | + return os.environ.get("SIMULATE_SONIC", "0") == "1" |
| 46 | + |
| 47 | + |
| 48 | +def _run(cmd, check=False): |
| 49 | + return subprocess.run(cmd, check=check, capture_output=True, text=True) |
| 50 | + |
| 51 | + |
| 52 | +def _mtu(): |
| 53 | + return int(os.environ.get("MTU", "9100")) |
| 54 | + |
| 55 | + |
| 56 | +def _templated(cmd_tmpl, **kw): |
| 57 | + """Split a command template and substitute {ifname}/{addr} placeholders.""" |
| 58 | + return shlex.split(cmd_tmpl.format(**kw)) |
| 59 | + |
| 60 | + |
| 61 | +def ensure_portchannel(lag_index): |
| 62 | + """Create the PortChannel<lag_index> netdev teamd would provide for a LAG.""" |
| 63 | + if not enabled(): |
| 64 | + return |
| 65 | + |
| 66 | + pc_if = "PortChannel{}".format(lag_index) |
| 67 | + mtu = _mtu() |
| 68 | + |
| 69 | + if _run(["ip", "link", "show", pc_if]).returncode != 0: |
| 70 | + if _run(["ip", "link", "add", pc_if, "type", "bond"]).returncode != 0: |
| 71 | + _run(["ip", "link", "add", pc_if, "type", "dummy"], check=False) |
| 72 | + |
| 73 | + _run(["ip", "link", "set", "dev", pc_if, "mtu", str(mtu)], check=False) |
| 74 | + _run(["ip", "link", "set", "dev", pc_if, "up"], check=False) |
| 75 | + |
| 76 | + |
| 77 | +def assign_lag_rif_ips(lag_index, retries=20, interval=0.3): |
| 78 | + """Assign the connected IPs IntfMgr would put on a LAG router interface. |
| 79 | +
|
| 80 | + The IP is placed on the backend's LAG host-interface netdev (default "be<N>"), |
| 81 | + which the backend mirrors onto the LAG router interface. |
| 82 | + """ |
| 83 | + if not enabled() or os.environ.get("LAG_RIF_IPS", "1") != "1": |
| 84 | + return |
| 85 | + |
| 86 | + be_prefix = os.environ.get("LAG_BE_TAP_PREFIX", "be") |
| 87 | + v4_pattern = os.environ.get("LAG_RIF_IPV4_PATTERN", "10.1.%d.1/24") |
| 88 | + v6_pattern = os.environ.get("LAG_RIF_IPV6_PATTERN", "fc00:1::%d:1/112") |
| 89 | + subnet_id = lag_index + 1 |
| 90 | + be_if = "{}{}".format(be_prefix, lag_index) |
| 91 | + v4_addr = v4_pattern % subnet_id |
| 92 | + v6_addr = v6_pattern % subnet_id |
| 93 | + |
| 94 | + for _ in range(retries): |
| 95 | + if _run(["ip", "link", "show", be_if]).returncode == 0: |
| 96 | + break |
| 97 | + time.sleep(interval) |
| 98 | + else: |
| 99 | + return |
| 100 | + |
| 101 | + disable_v6 = "/proc/sys/net/ipv6/conf/{}/disable_ipv6".format(be_if) |
| 102 | + accept_dad = "/proc/sys/net/ipv6/conf/{}/accept_dad".format(be_if) |
| 103 | + try: |
| 104 | + with open(disable_v6, "w", encoding="ascii") as fh: |
| 105 | + fh.write("0") |
| 106 | + with open(accept_dad, "w", encoding="ascii") as fh: |
| 107 | + fh.write("0") |
| 108 | + except OSError: |
| 109 | + pass |
| 110 | + |
| 111 | + _run(["ip", "addr", "add", v4_addr, "dev", be_if], check=False) |
| 112 | + _run(["ip", "-6", "addr", "add", v6_addr, "dev", be_if, "nodad"], check=False) |
| 113 | + |
| 114 | + |
| 115 | +def _svi_group_id(vlan_id): |
| 116 | + for entry in os.environ.get("SVI_RIF_VLANS", "10:1 20:2").split(): |
| 117 | + vid, gid = entry.split(":", 1) |
| 118 | + if int(vid) == int(vlan_id): |
| 119 | + return int(gid) |
| 120 | + return int(vlan_id) |
| 121 | + |
| 122 | + |
| 123 | +def assign_svi_rif_ips(vlan_id, retries=20, interval=0.3): |
| 124 | + """Assign the connected IPs IntfMgr would put on a VLAN (SVI) router interface. |
| 125 | +
|
| 126 | + A VLAN SVI has no host-interface netdev to mirror from, so the IP must be set on |
| 127 | + the backend interface directly. The backend-specific commands are injected by the |
| 128 | + harness as templates (with {ifname}/{addr} placeholders): SVI_RIF_SET_IP_CMD sets |
| 129 | + the address, and the optional SVI_RIF_PROBE_CMD waits until the interface exists. |
| 130 | + With SVI_RIF_SET_IP_CMD unset this is a no-op, keeping this module backend-neutral. |
| 131 | + """ |
| 132 | + if not enabled() or os.environ.get("SVI_RIF_IPS", "1") != "1": |
| 133 | + return |
| 134 | + |
| 135 | + set_ip_cmd = os.environ.get("SVI_RIF_SET_IP_CMD") |
| 136 | + if not set_ip_cmd: |
| 137 | + return |
| 138 | + |
| 139 | + probe_cmd = os.environ.get("SVI_RIF_PROBE_CMD") |
| 140 | + bvi_prefix = os.environ.get("SVI_BVI_PREFIX", "bvi") |
| 141 | + v4_pattern = os.environ.get("SVI_RIF_IPV4_PATTERN", "192.168.%d.1/24") |
| 142 | + v6_pattern = os.environ.get("SVI_RIF_IPV6_PATTERN", "fc02::%d:1/112") |
| 143 | + group_id = _svi_group_id(vlan_id) |
| 144 | + bvi_if = "{}{}".format(bvi_prefix, vlan_id) |
| 145 | + v4_addr = v4_pattern % group_id |
| 146 | + v6_addr = v6_pattern % group_id |
| 147 | + |
| 148 | + if probe_cmd: |
| 149 | + for _ in range(retries): |
| 150 | + if _run(_templated(probe_cmd, ifname=bvi_if, addr="")).returncode == 0: |
| 151 | + break |
| 152 | + time.sleep(interval) |
| 153 | + else: |
| 154 | + return |
| 155 | + |
| 156 | + for addr in (v4_addr, v6_addr): |
| 157 | + _run(_templated(set_ip_cmd, ifname=bvi_if, addr=addr), check=False) |
0 commit comments