From 09e71714fb496f17ca8a6b0f751d934f99e1d2f1 Mon Sep 17 00:00:00 2001 From: Handa Wang Date: Thu, 1 Aug 2024 07:02:33 +0000 Subject: [PATCH 1/2] [Thread] support polling IPv4 packets in integration test This CL also refactors the IntegratoinTestUtils a bit to make it more reusable. Bug: 356770473 Test: CI Change-Id: Ief94e312e06e4b62c71284c8ff0ef710f0b632fb --- .../android/net/thread/BorderRoutingTest.java | 94 ++++++++++------ .../net/thread/utils/FullThreadDevice.java | 8 +- .../thread/utils/IntegrationTestUtils.java | 100 ++++++++++++++---- 3 files changed, 143 insertions(+), 59 deletions(-) diff --git a/thread/tests/integration/src/android/net/thread/BorderRoutingTest.java b/thread/tests/integration/src/android/net/thread/BorderRoutingTest.java index b6d9aa3173..48f966fd6e 100644 --- a/thread/tests/integration/src/android/net/thread/BorderRoutingTest.java +++ b/thread/tests/integration/src/android/net/thread/BorderRoutingTest.java @@ -17,17 +17,20 @@ package android.net.thread; import static android.Manifest.permission.MANAGE_TEST_NETWORKS; +import static android.net.InetAddresses.parseNumericAddress; import static android.net.thread.utils.IntegrationTestUtils.DEFAULT_DATASET; import static android.net.thread.utils.IntegrationTestUtils.getIpv6LinkAddresses; +import static android.net.thread.utils.IntegrationTestUtils.isExpectedIcmpv4Packet; import static android.net.thread.utils.IntegrationTestUtils.isExpectedIcmpv6Packet; -import static android.net.thread.utils.IntegrationTestUtils.isFromIpv6Source; +import static android.net.thread.utils.IntegrationTestUtils.isFrom; import static android.net.thread.utils.IntegrationTestUtils.isInMulticastGroup; -import static android.net.thread.utils.IntegrationTestUtils.isToIpv6Destination; +import static android.net.thread.utils.IntegrationTestUtils.isTo; import static android.net.thread.utils.IntegrationTestUtils.joinNetworkAndWaitForOmr; import static android.net.thread.utils.IntegrationTestUtils.newPacketReader; import static android.net.thread.utils.IntegrationTestUtils.pollForPacket; import static android.net.thread.utils.IntegrationTestUtils.sendUdpMessage; import static android.net.thread.utils.IntegrationTestUtils.waitFor; +import static android.system.OsConstants.ICMP_ECHO; import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ECHO_REPLY_TYPE; import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ECHO_REQUEST_TYPE; @@ -44,11 +47,11 @@ import static org.junit.Assert.assertNull; import static java.util.Objects.requireNonNull; import android.content.Context; -import android.net.InetAddresses; import android.net.IpPrefix; import android.net.LinkAddress; import android.net.LinkProperties; import android.net.MacAddress; +import android.net.RouteInfo; import android.net.thread.utils.FullThreadDevice; import android.net.thread.utils.InfraNetworkDevice; import android.net.thread.utils.OtDaemonController; @@ -74,7 +77,9 @@ import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import java.net.Inet4Address; import java.net.Inet6Address; +import java.net.InetAddress; import java.time.Duration; import java.util.ArrayList; import java.util.List; @@ -89,11 +94,14 @@ public class BorderRoutingTest { private static final String TAG = BorderRoutingTest.class.getSimpleName(); private static final int NUM_FTD = 2; private static final Inet6Address GROUP_ADDR_SCOPE_5 = - (Inet6Address) InetAddresses.parseNumericAddress("ff05::1234"); + (Inet6Address) parseNumericAddress("ff05::1234"); private static final Inet6Address GROUP_ADDR_SCOPE_4 = - (Inet6Address) InetAddresses.parseNumericAddress("ff04::1234"); + (Inet6Address) parseNumericAddress("ff04::1234"); private static final Inet6Address GROUP_ADDR_SCOPE_3 = - (Inet6Address) InetAddresses.parseNumericAddress("ff03::1234"); + (Inet6Address) parseNumericAddress("ff03::1234"); + private static final Inet4Address IPV4_SERVER_ADDR = + (Inet4Address) parseNumericAddress("8.8.8.8"); + private static final String NAT64_CIDR = "192.168.255.0/24"; @Rule public final ThreadFeatureCheckerRule mThreadRule = new ThreadFeatureCheckerRule(); @@ -165,7 +173,7 @@ public class BorderRoutingTest { mInfraDevice.sendEchoRequest(ftd.getOmrAddress()); // Infra device receives an echo reply sent by FTD. - assertNotNull(pollForPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftd.getOmrAddress())); + assertNotNull(pollForIcmpPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftd.getOmrAddress())); } @Test @@ -186,7 +194,7 @@ public class BorderRoutingTest { mInfraDevice.sendEchoRequest(ftd.getOmrAddress()); - assertNotNull(pollForPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftd.getOmrAddress())); + assertNotNull(pollForIcmpPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftd.getOmrAddress())); } @Test @@ -213,7 +221,7 @@ public class BorderRoutingTest { mInfraDevice.sendEchoRequest(ftd.getOmrAddress()); - assertNotNull(pollForPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftdOmr)); + assertNotNull(pollForIcmpPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftdOmr)); } finally { runAsShell(MANAGE_TEST_NETWORKS, () -> oldInfraNetworkTracker.teardown()); } @@ -322,7 +330,7 @@ public class BorderRoutingTest { mInfraDevice.sendEchoRequest(GROUP_ADDR_SCOPE_5); - assertNotNull(pollForPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftd.getOmrAddress())); + assertNotNull(pollForIcmpPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftd.getOmrAddress())); } @Test @@ -354,7 +362,7 @@ public class BorderRoutingTest { mInfraDevice.sendEchoRequest(GROUP_ADDR_SCOPE_3); - assertNull(pollForPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftd.getOmrAddress())); + assertNull(pollForIcmpPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftd.getOmrAddress())); } @Test @@ -375,7 +383,7 @@ public class BorderRoutingTest { mInfraDevice.sendEchoRequest(GROUP_ADDR_SCOPE_4); - assertNull(pollForPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftd.getOmrAddress())); + assertNull(pollForIcmpPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftd.getOmrAddress())); } @Test @@ -405,13 +413,15 @@ public class BorderRoutingTest { mInfraDevice.sendEchoRequest(GROUP_ADDR_SCOPE_5); - assertNotNull(pollForPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftd1.getOmrAddress())); + assertNotNull( + pollForIcmpPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftd1.getOmrAddress())); // Verify ping reply from ftd1 and ftd2 separately as the order of replies can't be // predicted. mInfraDevice.sendEchoRequest(GROUP_ADDR_SCOPE_4); - assertNotNull(pollForPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftd2.getOmrAddress())); + assertNotNull( + pollForIcmpPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftd2.getOmrAddress())); } @Test @@ -441,12 +451,14 @@ public class BorderRoutingTest { mInfraDevice.sendEchoRequest(GROUP_ADDR_SCOPE_5); - assertNotNull(pollForPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftd1.getOmrAddress())); + assertNotNull( + pollForIcmpPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftd1.getOmrAddress())); // Send the request twice as the order of replies from ftd1 and ftd2 are not guaranteed mInfraDevice.sendEchoRequest(GROUP_ADDR_SCOPE_5); - assertNotNull(pollForPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftd2.getOmrAddress())); + assertNotNull( + pollForIcmpPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftd2.getOmrAddress())); } @Test @@ -469,9 +481,11 @@ public class BorderRoutingTest { ftd.ping(GROUP_ADDR_SCOPE_4); assertNotNull( - pollForPacketOnInfraNetwork(ICMPV6_ECHO_REQUEST_TYPE, ftdOmr, GROUP_ADDR_SCOPE_5)); + pollForIcmpPacketOnInfraNetwork( + ICMPV6_ECHO_REQUEST_TYPE, ftdOmr, GROUP_ADDR_SCOPE_5)); assertNotNull( - pollForPacketOnInfraNetwork(ICMPV6_ECHO_REQUEST_TYPE, ftdOmr, GROUP_ADDR_SCOPE_4)); + pollForIcmpPacketOnInfraNetwork( + ICMPV6_ECHO_REQUEST_TYPE, ftdOmr, GROUP_ADDR_SCOPE_4)); } @Test @@ -493,7 +507,7 @@ public class BorderRoutingTest { ftd.ping(GROUP_ADDR_SCOPE_3); assertNull( - pollForPacketOnInfraNetwork( + pollForIcmpPacketOnInfraNetwork( ICMPV6_ECHO_REQUEST_TYPE, ftd.getOmrAddress(), GROUP_ADDR_SCOPE_3)); } @@ -517,7 +531,8 @@ public class BorderRoutingTest { ftd.ping(GROUP_ADDR_SCOPE_4, ftdLla); assertNull( - pollForPacketOnInfraNetwork(ICMPV6_ECHO_REQUEST_TYPE, ftdLla, GROUP_ADDR_SCOPE_4)); + pollForIcmpPacketOnInfraNetwork( + ICMPV6_ECHO_REQUEST_TYPE, ftdLla, GROUP_ADDR_SCOPE_4)); } @Test @@ -541,7 +556,7 @@ public class BorderRoutingTest { ftd.ping(GROUP_ADDR_SCOPE_4, ftdMla); assertNull( - pollForPacketOnInfraNetwork( + pollForIcmpPacketOnInfraNetwork( ICMPV6_ECHO_REQUEST_TYPE, ftdMla, GROUP_ADDR_SCOPE_4)); } } @@ -572,7 +587,7 @@ public class BorderRoutingTest { mInfraDevice.sendEchoRequest(GROUP_ADDR_SCOPE_5); - assertNotNull(pollForPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftdOmr)); + assertNotNull(pollForIcmpPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftdOmr)); } @Test @@ -600,7 +615,10 @@ public class BorderRoutingTest { ftd.ping(GROUP_ADDR_SCOPE_4); assertNotNull( - pollForPacketOnInfraNetwork(ICMPV6_ECHO_REQUEST_TYPE, ftdOmr, GROUP_ADDR_SCOPE_4)); + pollForIcmpPacketOnInfraNetwork( + ICMPV6_ECHO_REQUEST_TYPE, ftdOmr, GROUP_ADDR_SCOPE_4)); + } + } private void setUpInfraNetwork() throws Exception { @@ -648,20 +666,28 @@ public class BorderRoutingTest { assertInfraLinkMemberOfGroup(address); } - private byte[] pollForPacketOnInfraNetwork(int type, Inet6Address srcAddress) { - return pollForPacketOnInfraNetwork(type, srcAddress, null); + private byte[] pollForIcmpPacketOnInfraNetwork(int type, InetAddress srcAddress) { + return pollForIcmpPacketOnInfraNetwork(type, srcAddress, null /* destAddress */); } - private byte[] pollForPacketOnInfraNetwork( - int type, Inet6Address srcAddress, Inet6Address destAddress) { - Predicate filter; - filter = + private byte[] pollForIcmpPacketOnInfraNetwork( + int type, InetAddress srcAddress, InetAddress destAddress) { + if (srcAddress == null && destAddress == null) { + throw new IllegalArgumentException("srcAddress and destAddress cannot be both null"); + } + if (srcAddress != null && destAddress != null) { + if ((srcAddress instanceof Inet4Address) != (destAddress instanceof Inet4Address)) { + throw new IllegalArgumentException( + "srcAddress and destAddress must be both IPv4 or both IPv6"); + } + } + boolean isIpv4 = + (srcAddress instanceof Inet4Address) || (destAddress instanceof Inet4Address); + final Predicate filter = p -> - (isExpectedIcmpv6Packet(p, type) - && (srcAddress == null ? true : isFromIpv6Source(p, srcAddress)) - && (destAddress == null - ? true - : isToIpv6Destination(p, destAddress))); + (isIpv4 ? isExpectedIcmpv4Packet(p, type) : isExpectedIcmpv6Packet(p, type)) + && (srcAddress == null || isFrom(p, srcAddress)) + && (destAddress == null || isTo(p, destAddress)); return pollForPacket(mInfraNetworkReader, filter); } } diff --git a/thread/tests/integration/src/android/net/thread/utils/FullThreadDevice.java b/thread/tests/integration/src/android/net/thread/utils/FullThreadDevice.java index 8440bbcd5c..083a84106e 100644 --- a/thread/tests/integration/src/android/net/thread/utils/FullThreadDevice.java +++ b/thread/tests/integration/src/android/net/thread/utils/FullThreadDevice.java @@ -417,7 +417,7 @@ public final class FullThreadDevice { executeCommand("ipmaddr add " + address.getHostAddress()); } - public void ping(Inet6Address address, Inet6Address source) { + public void ping(InetAddress address, Inet6Address source) { ping( address, source, @@ -428,7 +428,7 @@ public final class FullThreadDevice { PING_TIMEOUT_0_1_SECOND); } - public void ping(Inet6Address address) { + public void ping(InetAddress address) { ping( address, null, @@ -440,7 +440,7 @@ public final class FullThreadDevice { } /** Returns the number of ping reply packets received. */ - public int ping(Inet6Address address, int count) { + public int ping(InetAddress address, int count) { List output = ping( address, @@ -454,7 +454,7 @@ public final class FullThreadDevice { } private List ping( - Inet6Address address, + InetAddress address, Inet6Address source, int size, int count, diff --git a/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.java b/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.java index 7b0c4150a5..82e9332dda 100644 --- a/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.java +++ b/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.java @@ -16,6 +16,7 @@ package android.net.thread.utils; import static android.net.NetworkCapabilities.NET_CAPABILITY_LOCAL_NETWORK; +import static android.system.OsConstants.IPPROTO_ICMP; import static android.system.OsConstants.IPPROTO_ICMPV6; import static com.android.compatibility.common.util.SystemUtil.runShellCommandOrThrow; @@ -49,7 +50,9 @@ import androidx.annotation.NonNull; import androidx.test.core.app.ApplicationProvider; import com.android.net.module.util.Struct; +import com.android.net.module.util.structs.Icmpv4Header; import com.android.net.module.util.structs.Icmpv6Header; +import com.android.net.module.util.structs.Ipv4Header; import com.android.net.module.util.structs.Ipv6Header; import com.android.net.module.util.structs.PrefixInformationOption; import com.android.net.module.util.structs.RaHeader; @@ -62,6 +65,7 @@ import java.io.FileDescriptor; import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; +import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; import java.net.InetSocketAddress; @@ -192,16 +196,36 @@ public final class IntegrationTestUtils { return null; } + /** Returns {@code true} if {@code packet} is an ICMPv4 packet of given {@code type}. */ + public static boolean isExpectedIcmpv4Packet(byte[] packet, int type) { + ByteBuffer buf = makeByteBuffer(packet); + Ipv4Header header = extractIpv4Header(buf); + if (header == null) { + return false; + } + if (header.protocol != (byte) IPPROTO_ICMP) { + return false; + } + try { + return Struct.parse(Icmpv4Header.class, buf).type == (short) type; + } catch (IllegalArgumentException ignored) { + // It's fine that the passed in packet is malformed because it's could be sent + // by anybody. + } + return false; + } + /** Returns {@code true} if {@code packet} is an ICMPv6 packet of given {@code type}. */ public static boolean isExpectedIcmpv6Packet(byte[] packet, int type) { - if (packet == null) { + ByteBuffer buf = makeByteBuffer(packet); + Ipv6Header header = extractIpv6Header(buf); + if (header == null) { + return false; + } + if (header.nextHeader != (byte) IPPROTO_ICMPV6) { return false; } - ByteBuffer buf = ByteBuffer.wrap(packet); try { - if (Struct.parse(Ipv6Header.class, buf).nextHeader != (byte) IPPROTO_ICMPV6) { - return false; - } return Struct.parse(Icmpv6Header.class, buf).type == (short) type; } catch (IllegalArgumentException ignored) { // It's fine that the passed in packet is malformed because it's could be sent @@ -210,32 +234,66 @@ public final class IntegrationTestUtils { return false; } - public static boolean isFromIpv6Source(byte[] packet, Inet6Address src) { - if (packet == null) { - return false; - } - ByteBuffer buf = ByteBuffer.wrap(packet); - try { - return Struct.parse(Ipv6Header.class, buf).srcIp.equals(src); - } catch (IllegalArgumentException ignored) { - // It's fine that the passed in packet is malformed because it's could be sent - // by anybody. + public static boolean isFrom(byte[] packet, InetAddress src) { + if (src instanceof Inet4Address) { + return isFromIpv4Source(packet, (Inet4Address) src); + } else if (src instanceof Inet6Address) { + return isFromIpv6Source(packet, (Inet6Address) src); } return false; } - public static boolean isToIpv6Destination(byte[] packet, Inet6Address dest) { - if (packet == null) { - return false; + public static boolean isTo(byte[] packet, InetAddress dest) { + if (dest instanceof Inet4Address) { + return isToIpv4Destination(packet, (Inet4Address) dest); + } else if (dest instanceof Inet6Address) { + return isToIpv6Destination(packet, (Inet6Address) dest); } - ByteBuffer buf = ByteBuffer.wrap(packet); + return false; + } + + private static boolean isFromIpv4Source(byte[] packet, Inet4Address src) { + Ipv4Header header = extractIpv4Header(makeByteBuffer(packet)); + return header != null && header.srcIp.equals(src); + } + + private static boolean isFromIpv6Source(byte[] packet, Inet6Address src) { + Ipv6Header header = extractIpv6Header(makeByteBuffer(packet)); + return header != null && header.srcIp.equals(src); + } + + private static boolean isToIpv4Destination(byte[] packet, Inet4Address dest) { + Ipv4Header header = extractIpv4Header(makeByteBuffer(packet)); + return header != null && header.dstIp.equals(dest); + } + + private static boolean isToIpv6Destination(byte[] packet, Inet6Address dest) { + Ipv6Header header = extractIpv6Header(makeByteBuffer(packet)); + return header != null && header.dstIp.equals(dest); + } + + private static ByteBuffer makeByteBuffer(byte[] packet) { + return packet == null ? null : ByteBuffer.wrap(packet); + } + + private static Ipv4Header extractIpv4Header(ByteBuffer buf) { try { - return Struct.parse(Ipv6Header.class, buf).dstIp.equals(dest); + return Struct.parse(Ipv4Header.class, buf); } catch (IllegalArgumentException ignored) { // It's fine that the passed in packet is malformed because it's could be sent // by anybody. } - return false; + return null; + } + + private static Ipv6Header extractIpv6Header(ByteBuffer buf) { + try { + return Struct.parse(Ipv6Header.class, buf); + } catch (IllegalArgumentException ignored) { + // It's fine that the passed in packet is malformed because it's could be sent + // by anybody. + } + return null; } /** Returns the Prefix Information Options (PIO) extracted from an ICMPv6 RA message. */ From d38b7cb30fef988f778988fc180871276cbadca0 Mon Sep 17 00:00:00 2001 From: Handa Wang Date: Thu, 1 Aug 2024 07:04:45 +0000 Subject: [PATCH 2/2] [Thread] add an integration test case for NAT64 This CL adds an integration test case for NAT64. Because the feature is not fully ready, we need some extra setup steps to make the feature work. That is, specifying a CIDR for NAT64. Also, this test case is based on `ot-ctl` but eventually the test should be based on API. This CL requires https://github.com/openthread/openthread/pull/10566. Bug: 356770473 Test: atest ThreadNetworkIntegrationTests Change-Id: Idfb5ad848f7b2369591e47558ea67017c0b31e19 --- .../android/net/thread/BorderRoutingTest.java | 29 +++++++++++++++---- .../net/thread/utils/OtDaemonController.java | 23 +++++++++++++++ 2 files changed, 47 insertions(+), 5 deletions(-) diff --git a/thread/tests/integration/src/android/net/thread/BorderRoutingTest.java b/thread/tests/integration/src/android/net/thread/BorderRoutingTest.java index 48f966fd6e..9e8dc3a994 100644 --- a/thread/tests/integration/src/android/net/thread/BorderRoutingTest.java +++ b/thread/tests/integration/src/android/net/thread/BorderRoutingTest.java @@ -619,17 +619,36 @@ public class BorderRoutingTest { ICMPV6_ECHO_REQUEST_TYPE, ftdOmr, GROUP_ADDR_SCOPE_4)); } + @Test + public void nat64_threadDevicePingIpv4InfraDevice_outboundPacketIsForwarded() throws Exception { + FullThreadDevice ftd = mFtds.get(0); + joinNetworkAndWaitForOmr(ftd, DEFAULT_DATASET); + // TODO: enable NAT64 via ThreadNetworkController API instead of ot-ctl + mOtCtl.setNat64Cidr(NAT64_CIDR); + mOtCtl.setNat64Enabled(true); + waitFor(() -> mOtCtl.hasNat64PrefixInNetdata(), Duration.ofSeconds(10)); + + ftd.ping(IPV4_SERVER_ADDR); + + assertNotNull(pollForIcmpPacketOnInfraNetwork(ICMP_ECHO, null, IPV4_SERVER_ADDR)); } private void setUpInfraNetwork() throws Exception { + LinkProperties lp = new LinkProperties(); + // NAT64 feature requires the infra network to have an IPv4 default route. + lp.addRoute( + new RouteInfo( + new IpPrefix("0.0.0.0/0") /* destination */, + null /* gateway */, + null, + RouteInfo.RTN_UNICAST, + 1500 /* mtu */)); mInfraNetworkTracker = runAsShell( MANAGE_TEST_NETWORKS, - () -> - initTestNetwork( - mContext, new LinkProperties(), 5000 /* timeoutMs */)); - mController.setTestNetworkAsUpstreamAndWait( - mInfraNetworkTracker.getTestIface().getInterfaceName()); + () -> initTestNetwork(mContext, lp, 5000 /* timeoutMs */)); + String infraNetworkName = mInfraNetworkTracker.getTestIface().getInterfaceName(); + mController.setTestNetworkAsUpstreamAndWait(infraNetworkName); } private void tearDownInfraNetwork() { diff --git a/thread/tests/integration/src/android/net/thread/utils/OtDaemonController.java b/thread/tests/integration/src/android/net/thread/utils/OtDaemonController.java index b3175fd1c9..15a3f5c074 100644 --- a/thread/tests/integration/src/android/net/thread/utils/OtDaemonController.java +++ b/thread/tests/integration/src/android/net/thread/utils/OtDaemonController.java @@ -105,6 +105,29 @@ public final class OtDaemonController { return prefixes.isEmpty() ? null : prefixes.get(0); } + /** Enables/Disables NAT64 feature. */ + public void setNat64Enabled(boolean enabled) { + executeCommand("nat64 " + (enabled ? "enable" : "disable")); + } + + /** Sets the NAT64 CIDR. */ + public void setNat64Cidr(String cidr) { + executeCommand("nat64 cidr " + cidr); + } + + /** Returns whether there's a NAT64 prefix in network data */ + public boolean hasNat64PrefixInNetdata() { + // Example (in the 'Routes' section): + // fdb2:bae3:5b59:2:0:0::/96 sn low c000 + List outputLines = executeCommandAndParse("netdata show"); + for (String line : outputLines) { + if (line.contains(" sn")) { + return true; + } + } + return false; + } + public String executeCommand(String cmd) { return SystemUtil.runShellCommand(OT_CTL + " " + cmd); }