diff --git a/test/case/ietf_interfaces/Readme.adoc b/test/case/ietf_interfaces/Readme.adoc
index 42f6061e..de861eb0 100644
--- a/test/case/ietf_interfaces/Readme.adoc
+++ b/test/case/ietf_interfaces/Readme.adoc
@@ -35,6 +35,8 @@ include::dual_bridge/Readme.adoc[]
include::lag_basic/Readme.adoc[]
+include::lag_failure/Readme.adoc[]
+
include::igmp_basic/Readme.adoc[]
include::igmp_vlan/Readme.adoc[]
diff --git a/test/case/ietf_interfaces/ietf_interfaces.yaml b/test/case/ietf_interfaces/ietf_interfaces.yaml
index 4709465b..88841fd3 100644
--- a/test/case/ietf_interfaces/ietf_interfaces.yaml
+++ b/test/case/ietf_interfaces/ietf_interfaces.yaml
@@ -38,6 +38,9 @@
- name: lag_basic
case: lag_basic/test.py
+- name: lag_failure
+ case: lag_failure/test.py
+
- name: bridge_fwd_sgl_dut
case: bridge_fwd_sgl_dut/test.py
diff --git a/test/case/ietf_interfaces/lag_failure/Readme.adoc b/test/case/ietf_interfaces/lag_failure/Readme.adoc
new file mode 120000
index 00000000..ede0d845
--- /dev/null
+++ b/test/case/ietf_interfaces/lag_failure/Readme.adoc
@@ -0,0 +1 @@
+lag_failure.adoc
\ No newline at end of file
diff --git a/test/case/ietf_interfaces/lag_failure/lag-failure.svg b/test/case/ietf_interfaces/lag_failure/lag-failure.svg
new file mode 100644
index 00000000..15963fed
--- /dev/null
+++ b/test/case/ietf_interfaces/lag_failure/lag-failure.svg
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/test/case/ietf_interfaces/lag_failure/lag_failure.adoc b/test/case/ietf_interfaces/lag_failure/lag_failure.adoc
new file mode 100644
index 00000000..92fa04f6
--- /dev/null
+++ b/test/case/ietf_interfaces/lag_failure/lag_failure.adoc
@@ -0,0 +1,42 @@
+=== LACP Aggregate w/ Degraded Link
+==== Description
+Verify communication over an LACP link aggregate when individual member
+links stop forwarding traffic, without carrier loss.
+
+.Logical network setup, link breakers (lb1 & lb2) here managed by host PC
+ifdef::topdoc[]
+image::../../test/case/ietf_interfaces/lag_failure/lag-failure.svg[]
+endif::topdoc[]
+ifndef::topdoc[]
+ifdef::testgroup[]
+image::lag_failure/lag-failure.svg[]
+endif::testgroup[]
+ifndef::testgroup[]
+image::lag-failure.svg[]
+endif::testgroup[]
+endif::topdoc[]
+
+The host verifies connectivity with dut2 via dut1 over the aggregate for
+each failure mode step using the `mon` interface.
+
+==== Topology
+ifdef::topdoc[]
+image::{topdoc}../../test/case/ietf_interfaces/lag_failure/topology.svg[LACP Aggregate w/ Degraded Link topology]
+endif::topdoc[]
+ifndef::topdoc[]
+ifdef::testgroup[]
+image::lag_failure/topology.svg[LACP Aggregate w/ Degraded Link topology]
+endif::testgroup[]
+ifndef::testgroup[]
+image::topology.svg[LACP Aggregate w/ Degraded Link topology]
+endif::testgroup[]
+endif::topdoc[]
+==== Test sequence
+. Set up topology and attach to target DUTs
+. Set up link aggregate, lag0, between dut1 and dut2
+. Initial connectivity check ...
+. Verify failure modes
+
+
+<<<
+
diff --git a/test/case/ietf_interfaces/lag_failure/test.py b/test/case/ietf_interfaces/lag_failure/test.py
new file mode 100755
index 00000000..315c2236
--- /dev/null
+++ b/test/case/ietf_interfaces/lag_failure/test.py
@@ -0,0 +1,143 @@
+#!/usr/bin/env python3
+r"""LACP Aggregate w/ Degraded Link
+
+Verify communication over an LACP link aggregate when individual member
+links stop forwarding traffic, without carrier loss.
+
+.Logical network setup, link breakers (lb1 & lb2) here managed by host PC
+image::lag-failure.svg[]
+
+The host verifies connectivity with dut2 via dut1 over the aggregate for
+each failure mode step using the `mon` interface.
+
+"""
+from time import time
+import infamy
+from infamy.netns import TPMR
+from infamy.util import parallel
+
+IPH = "192.168.2.1"
+IP1 = "192.168.2.41"
+IP2 = "192.168.2.42"
+
+
+class LinkBreaker:
+ """Encapsulates TPMR based link-breakers."""
+
+ def __init__(self, sys, netns):
+ self.net = netns
+ self.lb1 = TPMR(sys.ltop.xlate("host", "lb1a")[1],
+ sys.ltop.xlate("host", "lb1b")[1]).start()
+ self.lb2 = TPMR(sys.ltop.xlate("host", "lb2a")[1],
+ sys.ltop.xlate("host", "lb2b")[1]).start()
+
+ def forward(self, lb1, lb2):
+ """Set link breakers in forwarding or blocking state."""
+ getattr(self.lb1, lb1)()
+ getattr(self.lb2, lb2)()
+
+ def fail_check(self, peer):
+ """Verify connectivity with a given peer during failure."""
+ sequence = [
+ ("forward", "forward"),
+ ("forward", "block"),
+ ("block", "forward"),
+ ("forward", "forward")
+ ]
+
+ total_start = time()
+ print(f"{'LB1':<8} | {'LB2':<8} | {'Status':<8}")
+ print("---------|----------|---------")
+
+ for lb1, lb2 in sequence:
+ state_start = time()
+ try:
+ print(f"{lb1:<8} | {lb2:<8} | {'...':<8}", end="\r# ")
+ self.forward(lb1, lb2)
+ self.net.must_reach(peer, timeout=30)
+ print(f"{lb1:<8} | {lb2:<8} | {'OK':<8} in "
+ f"{time() - state_start:.2f}s")
+ except Exception as e:
+ print(f"{lb1:<8} | {lb2:<8} | {'FAIL':<8} after "
+ f"{time() - state_start:.2f}s")
+ print(f"\nError encountered: {e}")
+ print(f"Link breakers were in state: LB1='{lb1}', LB2='{lb2}'")
+ raise
+
+ print(f"Total time: {time() - total_start:.2f}s")
+
+
+def net_init(host, addr):
+ """Set up DUT network, dut1 bridges host port with lag0"""
+ if host:
+ net = [{
+ "name": "br0",
+ "type": "infix-if-type:bridge",
+ "ipv4": {
+ "address": [{"ip": addr, "prefix-length": 24}]
+ }
+ }, {
+ "name": host,
+ "bridge-port": {"bridge": "br0"}
+ }, {
+ "name": "lag0",
+ "bridge-port": {"bridge": "br0"}
+ }]
+ else:
+ net = [{
+ "name": "lag0",
+ "ipv4": {
+ "address": [{"ip": addr, "prefix-length": 24}]
+ }
+ }]
+ return net
+
+
+def dut_init(dut, addr, peer):
+ """Configure each DUT specific according to LAG mode and peer"""
+ net = net_init(dut["mon"], addr)
+
+ dut.put_config_dict("ietf-interfaces", {
+ "interfaces": {
+ "interface": [{
+ "name": "lag0",
+ "type": "infix-if-type:lag",
+ "lag": {
+ "mode": "lacp",
+ "lacp": {"rate": "fast"},
+ "link-monitor": {"interval": 100}
+ }
+ }, {
+ "name": dut["link1"],
+ "lag-port": {"lag": "lag0"}
+ }, {
+ "name": dut["link2"],
+ "lag-port": {"lag": "lag0"}
+ }] + net
+ }
+ })
+
+
+with infamy.Test() as test:
+ with test.step("Set up topology and attach to target DUTs"):
+ env = infamy.Env()
+ dut1 = env.attach("dut1")
+ dut2 = env.attach("dut2")
+
+ _, mon = env.ltop.xlate("host", "mon")
+ with infamy.IsolatedMacVlan(mon) as ns:
+ lb = LinkBreaker(env, ns)
+ ns.addip(IPH)
+
+ print(f"Setting up lag0 in LACP mode between {dut1} and {dut2}")
+ with test.step("Set up link aggregate, lag0, between dut1 and dut2"):
+ parallel(lambda: dut_init(dut1, IP1, IP2),
+ lambda: dut_init(dut2, IP2, IP1))
+
+ with test.step("Initial connectivity check ..."):
+ ns.must_reach(IP2, timeout=30)
+
+ with test.step("Verify failure modes"):
+ lb.fail_check(IP2)
+
+ test.succeed()
diff --git a/test/case/ietf_interfaces/lag_failure/topology.dot b/test/case/ietf_interfaces/lag_failure/topology.dot
new file mode 100644
index 00000000..8b24b884
--- /dev/null
+++ b/test/case/ietf_interfaces/lag_failure/topology.dot
@@ -0,0 +1,36 @@
+graph "lag" {
+ layout="neato";
+ overlap="false";
+ esep="+23";
+
+ node [shape=record, fontsize=12, fontname="DejaVu Sans Mono, Book"];
+ edge [color="cornflowerblue", penwidth="2", fontname="DejaVu Serif, Book"];
+
+ host [
+ label="{{ mgmt1 | mon | lb1a | lb2a | lb2b | lb1b | mgmt2 } | host}",
+ pos="9,0!",
+ requires="controller",
+ ];
+
+ dut1 [
+ label="{ dut1\l | { mgmt | mon | link1 | link2 } }",
+ pos="0,6!",
+ requires="infix",
+ ];
+
+ dut2 [
+ label="{ dut2\r | { link2 | link1 | mgmt } }",
+ pos="18,6!",
+ requires="infix",
+ ];
+
+ host:mgmt1 -- dut1:mgmt [requires="mgmt", color=lightgray]
+ host:mon -- dut1:mon // Monitor connection to dut2 via dut1
+ host:mgmt2 -- dut2:mgmt [requires="mgmt", color=lightgrey]
+
+ dut1:link1 -- host:lb1a [requires="ieee-mc", color=black, fontcolor=black]
+ host:lb1b -- dut2:link1 [requires="ieee-mc", color=black, fontcolor=black]
+
+ dut1:link2 -- host:lb2a [requires="ieee-mc", color=black, fontcolor=black]
+ host:lb2b -- dut2:link2 [requires="ieee-mc", color=black, fontcolor=black]
+}
diff --git a/test/case/ietf_interfaces/lag_failure/topology.svg b/test/case/ietf_interfaces/lag_failure/topology.svg
new file mode 100644
index 00000000..44888247
--- /dev/null
+++ b/test/case/ietf_interfaces/lag_failure/topology.svg
@@ -0,0 +1,93 @@
+
+
+
+
+