Skip to content

Ospf sr - #488

Open
SouravSinhaRoy wants to merge 7 commits into
masterfrom
ospf_sr
Open

Ospf sr#488
SouravSinhaRoy wants to merge 7 commits into
masterfrom
ospf_sr

Conversation

@SouravSinhaRoy

@SouravSinhaRoy SouravSinhaRoy commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Feature Overview

  • Related Issue: (e.g., Fixes #<issue_number>)
  • Brief Description:
    Support of OSPF SR Capabilities, in OTG-Model and Snappi

Feature Details


Code snippets


 # ---- Devices and Ethernet ------------------------------------------------
    # p1 and p2 face DIFFERENT DUT interfaces - each on its own /24 subnet.
    #   d1 (p1 side): IxN=1.1.1.2, DUT port-1=1.1.1.1
    #   d2 (p2 side): IxN=2.2.1.2, DUT port-2=2.2.1.1
    SRGB_BASE = 900000                 # DUT SRGB base (pin on the DUT: label = SRGB_BASE + index)
    P1_PREFIX_SID_INDEX = 101
    P2_PREFIX_SID_INDEX = 201

    d1_eth                      = d1.ethernets.add()
    d1_eth.name                 = "d1_eth"
    d1_eth.connection.port_name = p1.name
    d1_eth.mac                  = "00:00:00:01:01:01"
    d1_v4         = d1_eth.ipv4_addresses.add()
    d1_v4.name    = "d1_ipv4"
    d1_v4.address = "1.1.1.2"          # IxN port-1 address
    d1_v4.gateway = "1.1.1.1"          # DUT interface connected to port 1
    d1_v4.prefix  = 24

    d2_eth                      = d2.ethernets.add()
    d2_eth.name                 = "d2_eth"
    d2_eth.connection.port_name = p2.name
    d2_eth.mac                  = "00:00:00:02:02:02"
    d2_v4         = d2_eth.ipv4_addresses.add()
    d2_v4.name    = "d2_ipv4"
    d2_v4.address = "2.2.1.2"          # IxN port-2 address
    d2_v4.gateway = "2.2.1.1"          # DUT interface connected to port 2
    d2_v4.prefix  = 24

    # ---- OSPFv2 router r1 - Port 1 <-> DUT ----------------------------------
    r1                 = d1.ospfv2
    r1.name            = "r1"
    r1.store_lsa       = True          # keep learned LSAs so SR info is readable via get_states
    r1.router_id.custom = "1.1.1.2"

    r1_intf                     = r1.interfaces.add()
    r1_intf.name                = "r1_intf"
    r1_intf.ipv4_name           = "d1_ipv4"
    r1_intf.network_type.choice = "point_to_point"
    r1_intf.advanced.validate_received_mtu = False
    # SR Adjacency-SID (Extended Link Opaque LSA)
    r1_adj           = r1_intf.adjacency_sids.add()
    r1_adj.sid_values = [24001]
    r1_adj.p_flag     = True
    r1_adj.weight     = 0

    # Router-level SR: SR-Algorithm, SRGB, SRLB and Node Prefix-SID
    r1.segment_routing.algorithms = [0]
    r1_srgb              = r1.segment_routing.srgb_ranges.add()
    r1_srgb.starting_sid = SRGB_BASE
    r1_srgb.range        = 65536
    r1_srlb              = r1.segment_routing.srlb_ranges.add()
    r1_srlb.starting_sid = 15000
    r1_srlb.range        = 1000
    r1_node              = r1.segment_routing.node_prefix_sid   # loopback Node-SID
    r1_node.sid_index    = 1           # choice -> sid_index (V=0,L=0)
    r1_node.np_flag      = True
    r1_node.n_flag       = True
    r1_node.algorithm    = 0

    r1_routes         = r1.v4_routes.add()
    r1_routes.name    = "r1_v4_routes"
    r1_addr           = r1_routes.addresses.add()
    r1_addr.address   = "10.10.10.1"
    r1_addr.prefix    = 32
    r1_addr.count     = 1
    r1_psid              = r1_routes.prefix_sids.add()   # Prefix-SID (Extended Prefix Opaque LSA)
    r1_psid.sid_indices  = [P1_PREFIX_SID_INDEX]         # DUT installs label = SRGB_BASE + 101
    r1_psid.np_flag      = True
    r1_psid.algorithm    = 0

    # ---- OSPFv2 router r2 - Port 2 <-> DUT ----------------------------------
    r2                 = d2.ospfv2
    r2.name            = "r2"
    r2.store_lsa       = True
    r2.router_id.custom = "2.2.1.2"

    r2_intf                     = r2.interfaces.add()
    r2_intf.name                = "r2_intf"
    r2_intf.ipv4_name           = "d2_ipv4"
    r2_intf.network_type.choice = "point_to_point"
    r2_intf.advanced.validate_received_mtu = False
    r2_adj           = r2_intf.adjacency_sids.add()
    r2_adj.sid_values = [24002]
    r2_adj.p_flag     = True
    r2_adj.weight     = 0

    r2.segment_routing.algorithms = [0]
    r2_srgb              = r2.segment_routing.srgb_ranges.add()
    r2_srgb.starting_sid = SRGB_BASE
    r2_srgb.range        = 65536
    r2_srlb              = r2.segment_routing.srlb_ranges.add()
    r2_srlb.starting_sid = 15000
    r2_srlb.range        = 1000
    r2_node              = r2.segment_routing.node_prefix_sid
    r2_node.sid_index    = 2
    r2_node.np_flag      = True
    r2_node.n_flag       = True
    r2_node.algorithm    = 0

    r2_routes         = r2.v4_routes.add()
    r2_routes.name    = "r2_v4_routes"
    r2_addr           = r2_routes.addresses.add()
    r2_addr.address   = "20.20.20.1"
    r2_addr.prefix    = 32
    r2_addr.count     = 1
    r2_psid              = r2_routes.prefix_sids.add()
    r2_psid.sid_indices  = [P2_PREFIX_SID_INDEX]         # DUT installs label = SRGB_BASE + 201
    r2_psid.np_flag      = True
    r2_psid.algorithm    = 0

    # ---- SR-MPLS device-based flow: d1 -> DUT -> d2 -------------------------
    # tx=d1_v4 / rx=d2_v4: IxN resolves the DUT's MAC via ARP (no hardcoded MAC).
    # Top MPLS label = SRGB_BASE + P2 prefix-SID index; the SR-enabled DUT
    # label-forwards it out port 2 (PHP pops, inner IPv4 delivered to d2).
    f1                                = cfg.flows.add()
    f1.name                           = "sr_mpls_pdp1"
    f1.tx_rx.device.tx_names          = [r1_routes.name]
    f1.tx_rx.device.rx_names          = [r2_routes.name]
    f1.rate.pps                       = 100
    f1.duration.fixed_packets.packets = 200
    f1.metrics.enable                 = True

    eth_1                = f1.packet.add().ethernet
    eth_1.src.value      = d1_eth.mac
    eth_1.dst.auto                                 # ARP-resolved DUT MAC

    mpls_1               = f1.packet.add().mpls
    mpls_1.label.value           = SRGB_BASE + P2_PREFIX_SID_INDEX   # 900201 -> reach 10.20.20.1 via DUT SR
    mpls_1.bottom_of_stack.value = 1

    ip4_inner            = f1.packet.add().ipv4
    ip4_inner.src.value       = "10.10.10.1"
    ip4_inner.dst.value       = "20.20.20.1"
    ip4_inner.protocol.value  = 6              # TCP

    tcp_inner            = f1.packet.add().tcp
    tcp_inner.src_port.value = 1234
    tcp_inner.dst_port.value = 80

"""
OSPFv2 Segment Routing (SR-MPLS) simulated-topology example, port -> DUT -> port.
Python-snappi mirror of ospf_sr_st_ring_mesh_pdp_test.go (PR #488 style):
  * port1 emulated router (p1d1) + 3x3 grid of simulated routers; grid (1,2) links back to p1d1.
  * port2 emulated router (p2d1) + 3-node ring of simulated routers; ring node 2 links back to p2d1.
  * iBGP over loopbacks on grid (2,2) and ring 2; one IPv4 flow between two simulated routes via the DUT.
Only the IGP changed (ISIS -> OSPFv2 SR); simulated_link / BGP / flow are protocol-agnostic.
"""

import snappi

SRGB_BASE = 900000          # DUT SRGB base (label = SRGB_BASE + index); pin on the DUT
SRGB_RANGE = 65536
SRLB_START = 15000
SRLB_RANGE = 1000


def build_config(api):
    cfg = api.config()

    # ---- Ports -------------------------------------------------------------
    p1 = cfg.ports.add(name="p1")   # set .location to your chassis port in a real run
    p2 = cfg.ports.add(name="p2")

    # ---- Emulated devices (one per port, connected to the DUT) -------------
    d1 = cfg.devices.add(name="p1d1")
    d2 = cfg.devices.add(name="p2d1")

    sid = {"node": 1, "route": 300, "adj": 24001}   # unique SID counters across all routers

    # ---- Helpers: an OSPFv2 SR router / interface / route ------------------
    def new_router(dev, name, router_id):
        r = dev.ospfv2
        r.name = name
        r.store_lsa = True                          # keep learned LSAs for get_states
        r.router_id.custom = router_id
        r.segment_routing.algorithms = [0]          # SPF
        srgb = r.segment_routing.srgb_ranges.add(); srgb.starting_sid = SRGB_BASE; srgb.range = SRGB_RANGE
        srlb = r.segment_routing.srlb_ranges.add(); srlb.starting_sid = SRLB_START; srlb.range = SRLB_RANGE
        node = r.segment_routing.node_prefix_sid    # loopback Node-SID
        node.sid_index = sid["node"]; node.np_flag = True; node.n_flag = True; node.algorithm = 0
        sid["node"] += 1
        return r

    def add_intf(r, name, ipv4_name):
        intf = r.interfaces.add()
        intf.name = name
        intf.ipv4_name = ipv4_name
        intf.network_type.choice = "point_to_point"
        intf.advanced.validate_received_mtu = False
        adj = intf.adjacency_sids.add()             # SR Adjacency-SID
        adj.sid_values = [sid["adj"]]; adj.p_flag = True; adj.weight = 0
        sid["adj"] += 1
        return intf

    def add_v4_route(r, name, addr, prefix, count, metric):
        v4 = r.v4_routes.add()
        v4.name = name; v4.metric = metric
        v4.route_origin.intra_area                  # route type (Stub / intra-area)
        a = v4.addresses.add(); a.address = addr; a.prefix = prefix; a.count = count; a.step = 1
        ps = v4.prefix_sids.add()                   # SR Prefix-SID (index into SRGB)
        ps.sid_indices = [sid["route"]]; ps.np_flag = True; ps.algorithm = 0
        sid["route"] += 1
        return v4

    def sim_link(dev, eth_name, remote_eth_name, addr, gateway):
        eth = dev.ethernets.add()
        eth.name = eth_name; eth.mac = "00:00:dd:ee:aa:dd"
        eth.connection.simulated_link.remote_simulated_link = remote_eth_name   # internal link
        ip = eth.ipv4_addresses.add()
        ip.name = eth.name + ".ipv4"; ip.address = addr; ip.gateway = gateway; ip.prefix = 24
        return eth, ip

    simulated = {}     # name -> device
    router_ids = {}    # name -> router-id / loopback ip

    # ---- port1 emulated router (p1d1) <-> DUT Ethernet1 (1.1.1.0/24) -------
    d1_eth = d1.ethernets.add()
    d1_eth.name = "p1d1Eth1"; d1_eth.mac = "00:00:01:01:01:01"; d1_eth.mtu = 1500
    d1_eth.connection.port_name = p1.name
    d1_v4 = d1_eth.ipv4_addresses.add()
    d1_v4.name = "p1d1ipv4"; d1_v4.address = "1.1.1.2"; d1_v4.gateway = "1.1.1.1"; d1_v4.prefix = 24

    d1ospf = new_router(d1, "p1d1Ospfv2", "1.1.1.2")
    add_v4_route(d1ospf, "d1ospf_v4rr", "101.1.1.1", 32, 1, 10)
    add_intf(d1ospf, "p1d1int", "p1d1ipv4")

    # ---- Simulated routers: 3x3 grid behind p1d1 ---------------------------
    rows, cols = 3, 3
    for row in range(rows):
        for col in range(cols):
            name = "p1.sim.%d.%d" % (row, col)
            sim = cfg.devices.add(name=name)
            rid = "10.10.%d.%d" % (row, col)
            sim_ospf = new_router(sim, name + ".ospfv2", rid)
            simulated[name] = sim; router_ids[name] = rid

            for trow in range(row - 1, row + 2):
                for tcol in range(col - 1, col + 2):
                    if 0 <= trow < rows and 0 <= tcol < cols and \
                       ((trow == row and tcol != col) or (trow != row and tcol == col)):
                        tname = "p1.sim.%d.%d" % (trow, tcol)
                        if row > trow or col > tcol:
                            o2, o3 = (trow * rows + tcol) + 1, (row * rows + col) + 1
                            addr, gw = "10.%d.%d.2" % (o2, o3), "10.%d.%d.1" % (o2, o3)
                        else:
                            o2, o3 = (row * rows + col) + 1, (trow * rows + tcol) + 1
                            addr, gw = "10.%d.%d.1" % (o2, o3), "10.%d.%d.2" % (o2, o3)
                        eth, _ = sim_link(sim, name + "-" + tname, tname + "-" + name, addr, gw)
                        add_intf(sim_ospf, name + "-" + tname + ".ospfv2", eth.name + ".ipv4")

            add_v4_route(sim_ospf, name + ".v4routes", "10.10.%d.%d" % (row + 1, col + 1), 32, 1, 10)

    # grid node (1,2) also links back to the emulated router p1d1
    gc = "p1.sim.1.2"; o3 = (1 * rows + 2) + 1
    ge, _ = sim_link(simulated[gc], gc + "-p1d1", "p1d1-" + gc, "10.0.%d.2" % o3, "10.0.%d.1" % o3)
    add_intf(simulated[gc].ospfv2, gc + "-p1d1.ospfv2", ge.name + ".ipv4")
    de, _ = sim_link(d1, "p1d1-" + gc, gc + "-p1d1", "10.0.%d.1" % o3, "10.0.%d.2" % o3)
    add_intf(d1ospf, "p1d1-" + gc + ".ospfv2", de.name + ".ipv4")

    # ---- port2 emulated router (p2d1) <-> DUT Ethernet2 (2.2.1.0/24) -------
    d2_eth = d2.ethernets.add()
    d2_eth.name = "p2d1Eth1"; d2_eth.mac = "00:00:02:02:02:02"; d2_eth.mtu = 1500
    d2_eth.connection.port_name = p2.name
    d2_v4 = d2_eth.ipv4_addresses.add()
    d2_v4.name = "p2d1ipv4"; d2_v4.address = "2.2.1.2"; d2_v4.gateway = "2.2.1.1"; d2_v4.prefix = 24

    d2ospf = new_router(d2, "p2d1Ospfv2", "2.2.1.2")
    add_v4_route(d1ospf, "d2ospf_v4rr", "201.1.1.1", 32, 1, 10)   # topology-fidelity extra (as in the Go test)
    add_intf(d2ospf, "p2d1int", "p2d1ipv4")

    # ---- Simulated routers: 3-node ring behind p2d1 ------------------------
    nodes = 3
    for node in range(nodes):
        name = "p2.sim.%d" % node
        sim = cfg.devices.add(name=name)
        rid = "11.11.0.%d" % node
        sim_ospf = new_router(sim, name + ".ospfv2", rid)
        simulated[name] = sim; router_ids[name] = rid

        lhs = node - 1 if node - 1 >= 0 else nodes - 1
        rhs = node + 1 if node + 1 < nodes else 0

        tname = "p2.sim.%d" % lhs
        eth, _ = sim_link(sim, name + "-" + tname, tname + "-" + name, "11.11.%d.2" % lhs, "11.11.%d.1" % lhs)
        add_intf(sim_ospf, name + "-" + tname + ".ospfv2", eth.name + ".ipv4")

        tname = "p2.sim.%d" % rhs
        eth, _ = sim_link(sim, name + "-" + tname, tname + "-" + name, "11.11.%d.1" % node, "11.11.%d.2" % node)
        add_intf(sim_ospf, name + "-" + tname + ".ospfv2", eth.name + ".ipv4")

        add_v4_route(sim_ospf, name + ".v4routes", "11.11.11.%d" % (node + 1), 32, 1, 10)

    # ring node 2 also links back to the emulated router p2d1
    rc = "p2.sim.2"
    re_, _ = sim_link(simulated[rc], rc + "-p2d1", "p2d1-" + rc, "11.0.2.2", "11.0.2.1")
    add_intf(simulated[rc].ospfv2, rc + "-p2d1.ospfv2", re_.name + ".ipv4")
    de2, _ = sim_link(d2, "p2d1-" + rc, rc + "-p2d1", "11.0.2.1", "11.0.2.2")
    add_intf(d2ospf, "p2d1-" + rc + ".ospfv2", de2.name + ".ipv4")

    # ---- Flow between two simulated routes, transiting the DUT -------------
    from_sim, to_sim = simulated["p1.sim.2.2"], simulated["p2.sim.2"]
    from_route = from_sim.ospfv2.v4_routes[0]
    to_route = to_sim.ospfv2.v4_routes[0]
    f1 = cfg.flows.add()
    f1.name = "IPv4 %s -> %s" % (from_sim.name, to_sim.name)
    f1.tx_rx.device.tx_names = [from_route.name]
    f1.tx_rx.device.rx_names = [to_route.name]
    f1.metrics.enable = True
    f1.duration.fixed_packets.packets = 1000
    f1.rate.pps = 200
    eth = f1.packet.add().ethernet
    eth.src.value = d1_eth.mac
    eth.dst.auto
    ip = f1.packet.add().ipv4
    ip.src.value = from_route.addresses[0].address
    ip.dst.value = to_route.addresses[0].address

    # ---- iBGP over loopbacks on the two simulated nodes --------------------
    from_ip, to_ip = router_ids["p1.sim.2.2"], router_ids["p2.sim.2"]
    for local, peer_ip in ((from_sim, to_ip), (to_sim, from_ip)):
        lo = local.ipv4_loopbacks.add()
        lo.name = local.name + ".lo"
        lo.address = from_ip if local is from_sim else to_ip
        lo.eth_name = local.ethernets[0].name
        bgp = local.bgp
        bgp.router_id = lo.address
        bgp_if = bgp.ipv4_interfaces.add()
        bgp_if.ipv4_name = lo.name
        peer = bgp_if.peers.add()
        peer.name = local.name + ".bgp"
        peer.as_number = 1111
        peer.as_type = "ibgp"
        peer.peer_address = peer_ip

    return cfg


if __name__ == "__main__":
    api = snappi.api()
    cfg = build_config(api)
    print("SERIALIZE_OK len=", len(cfg.serialize(cfg.JSON)))
    print("devices=", len(cfg.devices), " flows=", len(cfg.flows))


SouravSinhaRoy and others added 5 commits July 24, 2026 14:47
…safe_load() and no explicit encoding, so it uses the platform default codec — cp1252 on Windows. Any UTF-8 non-ASCII byte in a description then fails to decode locally. Linux CI defaults to UTF-8, which is why the committed artifacts built fine there but the build breaks on Windows machine.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants