• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 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;
18 
19 import static android.Manifest.permission.DUMP;
20 import static android.system.OsConstants.IPPROTO_UDP;
21 
22 import static com.android.testutils.DeviceInfoUtils.KVersion;
23 import static com.android.testutils.TestPermissionUtil.runAsShell;
24 
25 import static org.junit.Assert.assertEquals;
26 import static org.junit.Assert.assertFalse;
27 import static org.junit.Assert.assertNotNull;
28 import static org.junit.Assert.assertTrue;
29 import static org.junit.Assert.fail;
30 import static org.junit.Assume.assumeTrue;
31 
32 import android.content.Context;
33 import android.net.TetheringTester.TetheredDevice;
34 import android.os.Build;
35 import android.os.VintfRuntimeInfo;
36 import android.util.Log;
37 import android.util.Pair;
38 
39 import androidx.annotation.NonNull;
40 import androidx.annotation.Nullable;
41 import androidx.test.filters.MediumTest;
42 import androidx.test.runner.AndroidJUnit4;
43 
44 import com.android.net.module.util.BpfDump;
45 import com.android.net.module.util.Struct;
46 import com.android.net.module.util.bpf.Tether4Key;
47 import com.android.net.module.util.bpf.Tether4Value;
48 import com.android.net.module.util.bpf.TetherStatsKey;
49 import com.android.net.module.util.bpf.TetherStatsValue;
50 import com.android.testutils.DevSdkIgnoreRule;
51 import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
52 import com.android.testutils.DeviceInfoUtils;
53 import com.android.testutils.DumpTestUtils;
54 
55 import org.junit.Rule;
56 import org.junit.Test;
57 import org.junit.runner.RunWith;
58 
59 import java.net.InetAddress;
60 import java.util.Arrays;
61 import java.util.HashMap;
62 import java.util.Map;
63 
64 @RunWith(AndroidJUnit4.class)
65 @MediumTest
66 public class MtsEthernetTetheringTest extends EthernetTetheringTestBase {
67     @Rule
68     public final DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule();
69 
70     private static final String TAG = MtsEthernetTetheringTest.class.getSimpleName();
71 
72     private static final int DUMP_POLLING_MAX_RETRY = 100;
73     private static final int DUMP_POLLING_INTERVAL_MS = 50;
74     // Kernel treats a confirmed UDP connection which active after two seconds as stream mode.
75     // See upstream commit b7b1d02fc43925a4d569ec221715db2dfa1ce4f5.
76     private static final int UDP_STREAM_TS_MS = 2000;
77     // Give slack time for waiting UDP stream mode because handling conntrack event in user space
78     // may not in precise time. Used to reduce the flaky rate.
79     private static final int UDP_STREAM_SLACK_MS = 500;
80     // Per RX UDP packet size: iphdr (20) + udphdr (8) + payload (2) = 30 bytes.
81     private static final int RX_UDP_PACKET_SIZE = 30;
82     private static final int RX_UDP_PACKET_COUNT = 456;
83     // Per TX UDP packet size: iphdr (20) + udphdr (8) + payload (2) = 30 bytes.
84     private static final int TX_UDP_PACKET_SIZE = 30;
85     private static final int TX_UDP_PACKET_COUNT = 123;
86 
87     private static final String DUMPSYS_TETHERING_RAWMAP_ARG = "bpfRawMap";
88     private static final String DUMPSYS_RAWMAP_ARG_STATS = "--stats";
89     private static final String DUMPSYS_RAWMAP_ARG_UPSTREAM4 = "--upstream4";
90     private static final String LINE_DELIMITER = "\\n";
91 
isUdpOffloadSupportedByKernel(final String kernelVersion)92     private static boolean isUdpOffloadSupportedByKernel(final String kernelVersion) {
93         final KVersion current = DeviceInfoUtils.getMajorMinorSubminorVersion(kernelVersion);
94         return current.isInRange(new KVersion(4, 14, 222), new KVersion(4, 19, 0))
95                 || current.isInRange(new KVersion(4, 19, 176), new KVersion(5, 4, 0))
96                 || current.isAtLeast(new KVersion(5, 4, 98));
97     }
98 
99     @Test
testIsUdpOffloadSupportedByKernel()100     public void testIsUdpOffloadSupportedByKernel() throws Exception {
101         assertFalse(isUdpOffloadSupportedByKernel("4.14.221"));
102         assertTrue(isUdpOffloadSupportedByKernel("4.14.222"));
103         assertTrue(isUdpOffloadSupportedByKernel("4.16.0"));
104         assertTrue(isUdpOffloadSupportedByKernel("4.18.0"));
105         assertFalse(isUdpOffloadSupportedByKernel("4.19.0"));
106 
107         assertFalse(isUdpOffloadSupportedByKernel("4.19.175"));
108         assertTrue(isUdpOffloadSupportedByKernel("4.19.176"));
109         assertTrue(isUdpOffloadSupportedByKernel("5.2.0"));
110         assertTrue(isUdpOffloadSupportedByKernel("5.3.0"));
111         assertFalse(isUdpOffloadSupportedByKernel("5.4.0"));
112 
113         assertFalse(isUdpOffloadSupportedByKernel("5.4.97"));
114         assertTrue(isUdpOffloadSupportedByKernel("5.4.98"));
115         assertTrue(isUdpOffloadSupportedByKernel("5.10.0"));
116     }
117 
assumeKernelSupportBpfOffloadUdpV4()118     private static void assumeKernelSupportBpfOffloadUdpV4() {
119         final String kernelVersion = VintfRuntimeInfo.getKernelVersion();
120         assumeTrue("Kernel version " + kernelVersion + " doesn't support IPv4 UDP BPF offload",
121                 isUdpOffloadSupportedByKernel(kernelVersion));
122     }
123 
124     @Test
testKernelSupportBpfOffloadUdpV4()125     public void testKernelSupportBpfOffloadUdpV4() throws Exception {
126         assumeKernelSupportBpfOffloadUdpV4();
127     }
128 
isTetherConfigBpfOffloadEnabled()129     private boolean isTetherConfigBpfOffloadEnabled() throws Exception {
130         final String dumpStr = runAsShell(DUMP, () ->
131                 DumpTestUtils.dumpService(Context.TETHERING_SERVICE, "--short"));
132 
133         // BPF offload tether config can be overridden by "config_tether_enable_bpf_offload" in
134         // packages/modules/Connectivity/Tethering/res/values/config.xml. OEM may disable config by
135         // RRO to override the enabled default value. Get the tethering config via dumpsys.
136         // $ dumpsys tethering
137         //   mIsBpfEnabled: true
138         boolean enabled = dumpStr.contains("mIsBpfEnabled: true");
139         if (!enabled) {
140             Log.d(TAG, "BPF offload tether config not enabled: " + dumpStr);
141         }
142         return enabled;
143     }
144 
145     @Test
testTetherConfigBpfOffloadEnabled()146     public void testTetherConfigBpfOffloadEnabled() throws Exception {
147         assumeTrue(isTetherConfigBpfOffloadEnabled());
148     }
149 
150     @NonNull
dumpAndParseRawMap( Class<K> keyClass, Class<V> valueClass, @NonNull String mapArg)151     private <K extends Struct, V extends Struct> HashMap<K, V> dumpAndParseRawMap(
152             Class<K> keyClass, Class<V> valueClass, @NonNull String mapArg)
153             throws Exception {
154         final String[] args = new String[] {DUMPSYS_TETHERING_RAWMAP_ARG, mapArg};
155         final String rawMapStr = runAsShell(DUMP, () ->
156                 DumpTestUtils.dumpService(Context.TETHERING_SERVICE, args));
157         final HashMap<K, V> map = new HashMap<>();
158 
159         for (final String line : rawMapStr.split(LINE_DELIMITER)) {
160             final Pair<K, V> rule =
161                     BpfDump.fromBase64EncodedString(keyClass, valueClass, line.trim());
162             map.put(rule.first, rule.second);
163         }
164         return map;
165     }
166 
167     @Nullable
pollRawMapFromDump( Class<K> keyClass, Class<V> valueClass, @NonNull String mapArg)168     private <K extends Struct, V extends Struct> HashMap<K, V> pollRawMapFromDump(
169             Class<K> keyClass, Class<V> valueClass, @NonNull String mapArg)
170             throws Exception {
171         for (int retryCount = 0; retryCount < DUMP_POLLING_MAX_RETRY; retryCount++) {
172             final HashMap<K, V> map = dumpAndParseRawMap(keyClass, valueClass, mapArg);
173             if (!map.isEmpty()) return map;
174 
175             Thread.sleep(DUMP_POLLING_INTERVAL_MS);
176         }
177 
178         fail("Cannot get rules after " + DUMP_POLLING_MAX_RETRY * DUMP_POLLING_INTERVAL_MS + "ms");
179         return null;
180     }
181 
182     // Test network topology:
183     //
184     //         public network (rawip)                 private network
185     //                   |                 UE                |
186     // +------------+    V    +------------+------------+    V    +------------+
187     // |   Sever    +---------+  Upstream  | Downstream +---------+   Client   |
188     // +------------+         +------------+------------+         +------------+
189     // remote ip              public ip                           private ip
190     // 8.8.8.8:443            <Upstream ip>:9876                  <TetheredDevice ip>:9876
191     //
runUdp4Test()192     private void runUdp4Test() throws Exception {
193         final TetheringTester tester = initTetheringTester(toList(TEST_IP4_ADDR),
194                 toList(TEST_IP4_DNS));
195         final TetheredDevice tethered = tester.createTetheredDevice(TEST_MAC, false /* hasIpv6 */);
196 
197         // TODO: remove the connectivity verification for upstream connected notification race.
198         // Because async upstream connected notification can't guarantee the tethering routing is
199         // ready to use. Need to test tethering connectivity before testing.
200         // For short term plan, consider using IPv6 RA to get MAC address because the prefix comes
201         // from upstream. That can guarantee that the routing is ready. Long term plan is that
202         // refactors upstream connected notification from async to sync.
203         probeV4TetheringConnectivity(tester, tethered, false /* is4To6 */);
204 
205         final MacAddress srcMac = tethered.macAddr;
206         final MacAddress dstMac = tethered.routerMacAddr;
207         final InetAddress remoteIp = REMOTE_IP4_ADDR;
208         final InetAddress tetheringUpstreamIp = TEST_IP4_ADDR.getAddress();
209         final InetAddress clientIp = tethered.ipv4Addr;
210         sendUploadPacketUdp(srcMac, dstMac, clientIp, remoteIp, tester, false /* is4To6 */);
211         sendDownloadPacketUdp(remoteIp, tetheringUpstreamIp, tester, false /* is6To4 */);
212 
213         // Send second UDP packet in original direction.
214         // The BPF coordinator only offloads the ASSURED conntrack entry. The "request + reply"
215         // packets can make status IPS_SEEN_REPLY to be set. Need one more packet to make
216         // conntrack status IPS_ASSURED_BIT to be set. Note the third packet needs to delay
217         // 2 seconds because kernel monitors a UDP connection which still alive after 2 seconds
218         // and apply ASSURED flag.
219         // See kernel upstream commit b7b1d02fc43925a4d569ec221715db2dfa1ce4f5 and
220         // nf_conntrack_udp_packet in net/netfilter/nf_conntrack_proto_udp.c
221         Thread.sleep(UDP_STREAM_TS_MS);
222         sendUploadPacketUdp(srcMac, dstMac, clientIp, remoteIp, tester, false /* is4To6 */);
223 
224         // Give a slack time for handling conntrack event in user space.
225         Thread.sleep(UDP_STREAM_SLACK_MS);
226 
227         // [1] Verify IPv4 upstream rule map.
228         final HashMap<Tether4Key, Tether4Value> upstreamMap = pollRawMapFromDump(
229                 Tether4Key.class, Tether4Value.class, DUMPSYS_RAWMAP_ARG_UPSTREAM4);
230         assertNotNull(upstreamMap);
231         assertEquals(1, upstreamMap.size());
232 
233         final Map.Entry<Tether4Key, Tether4Value> rule =
234                 upstreamMap.entrySet().iterator().next();
235 
236         final Tether4Key upstream4Key = rule.getKey();
237         assertEquals(IPPROTO_UDP, upstream4Key.l4proto);
238         assertTrue(Arrays.equals(tethered.ipv4Addr.getAddress(), upstream4Key.src4));
239         assertEquals(LOCAL_PORT, upstream4Key.srcPort);
240         assertTrue(Arrays.equals(REMOTE_IP4_ADDR.getAddress(), upstream4Key.dst4));
241         assertEquals(REMOTE_PORT, upstream4Key.dstPort);
242 
243         final Tether4Value upstream4Value = rule.getValue();
244         assertTrue(Arrays.equals(tetheringUpstreamIp.getAddress(),
245                 InetAddress.getByAddress(upstream4Value.src46).getAddress()));
246         assertEquals(LOCAL_PORT, upstream4Value.srcPort);
247         assertTrue(Arrays.equals(REMOTE_IP4_ADDR.getAddress(),
248                 InetAddress.getByAddress(upstream4Value.dst46).getAddress()));
249         assertEquals(REMOTE_PORT, upstream4Value.dstPort);
250 
251         // [2] Verify stats map.
252         // Transmit packets on both direction for verifying stats. Because we only care the
253         // packet count in stats test, we just reuse the existing packets to increaes
254         // the packet count on both direction.
255 
256         // Send packets on original direction.
257         for (int i = 0; i < TX_UDP_PACKET_COUNT; i++) {
258             sendUploadPacketUdp(srcMac, dstMac, clientIp, remoteIp, tester,
259                     false /* is4To6 */);
260         }
261 
262         // Send packets on reply direction.
263         for (int i = 0; i < RX_UDP_PACKET_COUNT; i++) {
264             sendDownloadPacketUdp(remoteIp, tetheringUpstreamIp, tester, false /* is6To4 */);
265         }
266 
267         // Dump stats map to verify.
268         final HashMap<TetherStatsKey, TetherStatsValue> statsMap = pollRawMapFromDump(
269                 TetherStatsKey.class, TetherStatsValue.class, DUMPSYS_RAWMAP_ARG_STATS);
270         assertNotNull(statsMap);
271         assertEquals(1, statsMap.size());
272 
273         final Map.Entry<TetherStatsKey, TetherStatsValue> stats =
274                 statsMap.entrySet().iterator().next();
275 
276         // TODO: verify the upstream index in TetherStatsKey.
277 
278         final TetherStatsValue statsValue = stats.getValue();
279         assertEquals(RX_UDP_PACKET_COUNT, statsValue.rxPackets);
280         assertEquals(RX_UDP_PACKET_COUNT * RX_UDP_PACKET_SIZE, statsValue.rxBytes);
281         assertEquals(0, statsValue.rxErrors);
282         assertEquals(TX_UDP_PACKET_COUNT, statsValue.txPackets);
283         assertEquals(TX_UDP_PACKET_COUNT * TX_UDP_PACKET_SIZE, statsValue.txBytes);
284         assertEquals(0, statsValue.txErrors);
285     }
286 
287     /**
288      * BPF offload IPv4 UDP tethering test. Verify that UDP tethered packets are offloaded by BPF.
289      * Minimum test requirement:
290      * 1. S+ device.
291      * 2. Tethering config enables tethering BPF offload.
292      * 3. Kernel supports IPv4 UDP BPF offload. See #isUdpOffloadSupportedByKernel.
293      *
294      * TODO: consider enabling the test even tethering config disables BPF offload. See b/238288883
295      */
296     @Test
297     @IgnoreUpTo(Build.VERSION_CODES.R)
testTetherBpfOffloadUdpV4()298     public void testTetherBpfOffloadUdpV4() throws Exception {
299         assumeTrue("Tethering config disabled BPF offload", isTetherConfigBpfOffloadEnabled());
300         assumeKernelSupportBpfOffloadUdpV4();
301 
302         runUdp4Test();
303     }
304 }
305