1 /* 2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.net.thread.utils; 18 19 import android.annotation.Nullable; 20 import android.net.InetAddresses; 21 import android.net.IpPrefix; 22 import android.os.SystemClock; 23 24 import com.android.compatibility.common.util.SystemUtil; 25 26 import java.net.Inet6Address; 27 import java.util.Arrays; 28 import java.util.Collections; 29 import java.util.List; 30 31 /** 32 * Wrapper of the "/system/bin/ot-ctl" which can be used to send CLI commands to ot-daemon to 33 * control its behavior. 34 * 35 * <p>Note that this class takes root privileged to run. 36 */ 37 public final class OtDaemonController { 38 private static final String OT_CTL = "/system/bin/ot-ctl"; 39 40 /** 41 * Factory resets ot-daemon. 42 * 43 * <p>This will erase all persistent data written into apexdata/com.android.apex/ot-daemon and 44 * restart the ot-daemon service. 45 */ factoryReset()46 public void factoryReset() { 47 executeCommand("factoryreset"); 48 49 // TODO(b/323164524): ot-ctl is a separate process so that the tests can't depend on the 50 // time sequence. Here needs to wait for system server to receive the ot-daemon death 51 // signal and take actions. 52 // A proper fix is to replace "ot-ctl" with "cmd thread_network ot-ctl" which is 53 // synchronized with the system server 54 SystemClock.sleep(500); 55 } 56 57 /** Returns the output string of the "ot-ctl br state" command. */ getBorderRoutingState()58 public String getBorderRoutingState() { 59 return executeCommandAndParse("br state").getFirst(); 60 } 61 62 /** Returns the output string of the "ot-ctl srp server state" command. */ getSrpServerState()63 public String getSrpServerState() { 64 return executeCommandAndParse("srp server state").getFirst(); 65 } 66 67 /** Returns the list of IPv6 addresses on ot-daemon. */ getAddresses()68 public List<Inet6Address> getAddresses() { 69 return executeCommandAndParse("ipaddr").stream() 70 .map(addr -> InetAddresses.parseNumericAddress(addr)) 71 .map(inetAddr -> (Inet6Address) inetAddr) 72 .toList(); 73 } 74 75 /** Returns {@code true} if the Thread interface is up. */ isInterfaceUp()76 public boolean isInterfaceUp() { 77 String output = executeCommand("ifconfig"); 78 return output.contains("up"); 79 } 80 81 /** Returns the ML-EID of the device. */ getMlEid()82 public Inet6Address getMlEid() { 83 String addressStr = executeCommandAndParse("ipaddr mleid").get(0); 84 return (Inet6Address) InetAddresses.parseNumericAddress(addressStr); 85 } 86 87 /** Returns the country code on ot-daemon. */ getCountryCode()88 public String getCountryCode() { 89 return executeCommandAndParse("region").get(0); 90 } 91 92 /** 93 * Returns the list of IPv6 Mesh-Local addresses on ot-daemon. 94 * 95 * <p>The return List can be empty if no Mesh-Local prefix exists. 96 */ getMeshLocalAddresses()97 public List<Inet6Address> getMeshLocalAddresses() { 98 IpPrefix meshLocalPrefix = getMeshLocalPrefix(); 99 if (meshLocalPrefix == null) { 100 return Collections.emptyList(); 101 } 102 return getAddresses().stream().filter(addr -> meshLocalPrefix.contains(addr)).toList(); 103 } 104 105 /** 106 * Returns the Mesh-Local prefix or {@code null} if none exists (e.g. the Active Dataset is not 107 * set). 108 */ 109 @Nullable getMeshLocalPrefix()110 public IpPrefix getMeshLocalPrefix() { 111 List<IpPrefix> prefixes = 112 executeCommandAndParse("prefix meshlocal").stream() 113 .map(prefix -> new IpPrefix(prefix)) 114 .toList(); 115 return prefixes.isEmpty() ? null : prefixes.get(0); 116 } 117 118 /** Enables/Disables NAT64 feature. */ setNat64Enabled(boolean enabled)119 public void setNat64Enabled(boolean enabled) { 120 executeCommand("nat64 " + (enabled ? "enable" : "disable")); 121 } 122 123 /** Sets the NAT64 CIDR. */ setNat64Cidr(String cidr)124 public void setNat64Cidr(String cidr) { 125 executeCommand("nat64 cidr " + cidr); 126 } 127 128 /** Returns whether there's a NAT64 prefix in network data */ hasNat64PrefixInNetdata()129 public boolean hasNat64PrefixInNetdata() { 130 // Example (in the 'Routes' section): 131 // fdb2:bae3:5b59:2:0:0::/96 sn low c000 132 List<String> outputLines = executeCommandAndParse("netdata show"); 133 for (String line : outputLines) { 134 if (line.contains(" sn")) { 135 return true; 136 } 137 } 138 return false; 139 } 140 141 /** Adds a prefix in the Network Data. */ addPrefixInNetworkData(IpPrefix ipPrefix, String flags, String preference)142 public void addPrefixInNetworkData(IpPrefix ipPrefix, String flags, String preference) { 143 executeCommand("prefix add " + ipPrefix + " " + flags + " " + preference); 144 executeCommand("netdata register"); 145 } 146 getTrelPort()147 public int getTrelPort() { 148 return Integer.parseInt(executeCommandAndParse("trel port").get(0)); 149 } 150 getExtendedAddr()151 public String getExtendedAddr() { 152 return executeCommandAndParse("extaddr").get(0); 153 } 154 getExtendedPanId()155 public String getExtendedPanId() { 156 return executeCommandAndParse("extpanid").get(0); 157 } 158 executeCommand(String cmd)159 public String executeCommand(String cmd) { 160 return SystemUtil.runShellCommand(OT_CTL + " " + cmd); 161 } 162 163 /** 164 * Executes a ot-ctl command and parse the output to a list of strings. 165 * 166 * <p>The trailing "Done" in the command output will be dropped. 167 */ executeCommandAndParse(String cmd)168 public List<String> executeCommandAndParse(String cmd) { 169 return Arrays.asList(executeCommand(cmd).split("\n")).stream() 170 .map(String::trim) 171 .filter(str -> !str.equals("Done")) 172 .toList(); 173 } 174 } 175