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