• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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 com.android.net.module.util.NetworkStackConstants.ARP_REPLY;
20 import static com.android.net.module.util.NetworkStackConstants.ARP_REQUEST;
21 import static com.android.net.module.util.NetworkStackConstants.ETHER_ADDR_LEN;
22 import static com.android.net.module.util.NetworkStackConstants.ETHER_BROADCAST;
23 
24 import static org.junit.Assert.assertNotNull;
25 import static org.junit.Assert.fail;
26 
27 import android.net.dhcp.DhcpAckPacket;
28 import android.net.dhcp.DhcpOfferPacket;
29 import android.net.dhcp.DhcpPacket;
30 import android.util.ArrayMap;
31 import android.util.Log;
32 
33 import androidx.annotation.Nullable;
34 
35 import com.android.networkstack.arp.ArpPacket;
36 import com.android.testutils.TapPacketReader;
37 
38 import java.net.Inet4Address;
39 import java.nio.ByteBuffer;
40 import java.util.Random;
41 import java.util.concurrent.TimeoutException;
42 import java.util.function.Predicate;
43 
44 /**
45  * A class simulate tethered client. When caller create TetheringTester, it would connect to
46  * tethering module that do the dhcp and slaac to obtain ipv4 and ipv6 address. Then caller can
47  * send/receive packets by this class.
48  */
49 public final class TetheringTester {
50     private static final String TAG = TetheringTester.class.getSimpleName();
51     private static final int PACKET_READ_TIMEOUT_MS = 100;
52     private static final int DHCP_DISCOVER_ATTEMPTS = 10;
53     private static final byte[] DHCP_REQUESTED_PARAMS = new byte[] {
54             DhcpPacket.DHCP_SUBNET_MASK,
55             DhcpPacket.DHCP_ROUTER,
56             DhcpPacket.DHCP_DNS_SERVER,
57             DhcpPacket.DHCP_LEASE_TIME,
58     };
59 
60     public static final String DHCP_HOSTNAME = "testhostname";
61 
62     private final ArrayMap<MacAddress, TetheredDevice> mTetheredDevices;
63     private final TapPacketReader mDownstreamReader;
64 
TetheringTester(TapPacketReader downstream)65     public TetheringTester(TapPacketReader downstream) {
66         if (downstream == null) fail("Downstream reader could not be NULL");
67 
68         mDownstreamReader = downstream;
69         mTetheredDevices = new ArrayMap<>();
70     }
71 
createTetheredDevice(MacAddress macAddr)72     public TetheredDevice createTetheredDevice(MacAddress macAddr) throws Exception {
73         if (mTetheredDevices.get(macAddr) != null) {
74             fail("Tethered device already created");
75         }
76 
77         TetheredDevice tethered = new TetheredDevice(macAddr);
78         mTetheredDevices.put(macAddr, tethered);
79 
80         return tethered;
81     }
82 
83     public class TetheredDevice {
84         public final MacAddress macAddr;
85         public final MacAddress routerMacAddr;
86         public final Inet4Address ipv4Addr;
87 
TetheredDevice(MacAddress mac)88         private TetheredDevice(MacAddress mac) throws Exception {
89             macAddr = mac;
90 
91             DhcpResults dhcpResults = runDhcp(macAddr.toByteArray());
92             ipv4Addr = (Inet4Address) dhcpResults.ipAddress.getAddress();
93             routerMacAddr = getRouterMacAddressFromArp(ipv4Addr, macAddr,
94                     dhcpResults.serverAddress);
95         }
96     }
97 
98     /** Simulate dhcp client to obtain ipv4 address. */
runDhcp(byte[] clientMacAddr)99     public DhcpResults runDhcp(byte[] clientMacAddr)
100             throws Exception {
101         // We have to retransmit DHCP requests because IpServer declares itself to be ready before
102         // its DhcpServer is actually started. TODO: fix this race and remove this loop.
103         DhcpPacket offerPacket = null;
104         for (int i = 0; i < DHCP_DISCOVER_ATTEMPTS; i++) {
105             Log.d(TAG, "Sending DHCP discover");
106             sendDhcpDiscover(clientMacAddr);
107             offerPacket = getNextDhcpPacket();
108             if (offerPacket instanceof DhcpOfferPacket) break;
109         }
110         if (!(offerPacket instanceof DhcpOfferPacket)) {
111             throw new TimeoutException("No DHCPOFFER received on interface within timeout");
112         }
113 
114         sendDhcpRequest(offerPacket, clientMacAddr);
115         DhcpPacket ackPacket = getNextDhcpPacket();
116         if (!(ackPacket instanceof DhcpAckPacket)) {
117             throw new TimeoutException("No DHCPACK received on interface within timeout");
118         }
119 
120         return ackPacket.toDhcpResults();
121     }
122 
sendDhcpDiscover(byte[] macAddress)123     private void sendDhcpDiscover(byte[] macAddress) throws Exception {
124         ByteBuffer packet = DhcpPacket.buildDiscoverPacket(DhcpPacket.ENCAP_L2,
125                 new Random().nextInt() /* transactionId */, (short) 0 /* secs */,
126                 macAddress,  false /* unicast */, DHCP_REQUESTED_PARAMS,
127                 false /* rapid commit */,  DHCP_HOSTNAME);
128         mDownstreamReader.sendResponse(packet);
129     }
130 
sendDhcpRequest(DhcpPacket offerPacket, byte[] macAddress)131     private void sendDhcpRequest(DhcpPacket offerPacket, byte[] macAddress)
132             throws Exception {
133         DhcpResults results = offerPacket.toDhcpResults();
134         Inet4Address clientIp = (Inet4Address) results.ipAddress.getAddress();
135         Inet4Address serverIdentifier = results.serverAddress;
136         ByteBuffer packet = DhcpPacket.buildRequestPacket(DhcpPacket.ENCAP_L2,
137                 0 /* transactionId */, (short) 0 /* secs */, DhcpPacket.INADDR_ANY /* clientIp */,
138                 false /* broadcast */, macAddress, clientIp /* requestedIpAddress */,
139                 serverIdentifier, DHCP_REQUESTED_PARAMS, DHCP_HOSTNAME);
140         mDownstreamReader.sendResponse(packet);
141     }
142 
getNextDhcpPacket()143     private DhcpPacket getNextDhcpPacket() throws Exception {
144         final byte[] packet = getNextMatchedPacket((p) -> {
145             // Test whether this is DHCP packet.
146             try {
147                 DhcpPacket.decodeFullPacket(p, p.length, DhcpPacket.ENCAP_L2);
148             } catch (DhcpPacket.ParseException e) {
149                 // Not a DHCP packet.
150                 return false;
151             }
152 
153             return true;
154         });
155 
156         return packet == null ? null :
157                 DhcpPacket.decodeFullPacket(packet, packet.length, DhcpPacket.ENCAP_L2);
158     }
159 
160     @Nullable
parseArpPacket(final byte[] packet)161     private ArpPacket parseArpPacket(final byte[] packet) {
162         try {
163             return ArpPacket.parseArpPacket(packet, packet.length);
164         } catch (ArpPacket.ParseException e) {
165             return null;
166         }
167     }
168 
maybeReplyArp(byte[] packet)169     private void maybeReplyArp(byte[] packet) {
170         ByteBuffer buf = ByteBuffer.wrap(packet);
171 
172         final ArpPacket arpPacket = parseArpPacket(packet);
173         if (arpPacket == null || arpPacket.opCode != ARP_REQUEST) return;
174 
175         for (int i = 0; i < mTetheredDevices.size(); i++) {
176             TetheredDevice tethered = mTetheredDevices.valueAt(i);
177             if (!arpPacket.targetIp.equals(tethered.ipv4Addr)) continue;
178 
179             final ByteBuffer arpReply = ArpPacket.buildArpPacket(
180                     arpPacket.senderHwAddress.toByteArray() /* dst */,
181                     tethered.macAddr.toByteArray() /* srcMac */,
182                     arpPacket.senderIp.getAddress() /* target IP */,
183                     arpPacket.senderHwAddress.toByteArray() /* target HW address */,
184                     tethered.ipv4Addr.getAddress() /* sender IP */,
185                     (short) ARP_REPLY);
186             try {
187                 sendPacket(arpReply);
188             } catch (Exception e) {
189                 fail("Failed to reply ARP for " + tethered.ipv4Addr);
190             }
191             return;
192         }
193     }
194 
getRouterMacAddressFromArp(final Inet4Address tetherIp, final MacAddress tetherMac, final Inet4Address routerIp)195     private MacAddress getRouterMacAddressFromArp(final Inet4Address tetherIp,
196             final MacAddress tetherMac, final Inet4Address routerIp) throws Exception {
197         final ByteBuffer arpProbe = ArpPacket.buildArpPacket(ETHER_BROADCAST /* dst */,
198                 tetherMac.toByteArray() /* srcMac */, routerIp.getAddress() /* target IP */,
199                 new byte[ETHER_ADDR_LEN] /* target HW address */,
200                 tetherIp.getAddress() /* sender IP */, (short) ARP_REQUEST);
201         sendPacket(arpProbe);
202 
203         final byte[] packet = getNextMatchedPacket((p) -> {
204             final ArpPacket arpPacket = parseArpPacket(p);
205             if (arpPacket == null || arpPacket.opCode != ARP_REPLY) return false;
206             return arpPacket.targetIp.equals(tetherIp);
207         });
208 
209         if (packet != null) {
210             Log.d(TAG, "Get Mac address from ARP");
211             final ArpPacket arpReply = ArpPacket.parseArpPacket(packet, packet.length);
212             return arpReply.senderHwAddress;
213         }
214 
215         fail("Could not get ARP packet");
216         return null;
217     }
218 
sendPacket(ByteBuffer packet)219     public void sendPacket(ByteBuffer packet) throws Exception {
220         mDownstreamReader.sendResponse(packet);
221     }
222 
getNextMatchedPacket(Predicate<byte[]> filter)223     public byte[] getNextMatchedPacket(Predicate<byte[]> filter) {
224         byte[] packet;
225         while ((packet = mDownstreamReader.poll(PACKET_READ_TIMEOUT_MS)) != null) {
226             if (filter.test(packet)) return packet;
227 
228             maybeReplyArp(packet);
229         }
230 
231         return null;
232     }
233 
verifyUpload(final RemoteResponder dst, final ByteBuffer packet, final Predicate<byte[]> filter)234     public void verifyUpload(final RemoteResponder dst, final ByteBuffer packet,
235             final Predicate<byte[]> filter) throws Exception {
236         sendPacket(packet);
237         assertNotNull("Upload fail", dst.getNextMatchedPacket(filter));
238     }
239 
240     public static class RemoteResponder {
241         final TapPacketReader mUpstreamReader;
RemoteResponder(TapPacketReader reader)242         public RemoteResponder(TapPacketReader reader) {
243             mUpstreamReader = reader;
244         }
245 
sendPacket(ByteBuffer packet)246         public void sendPacket(ByteBuffer packet) throws Exception {
247             mUpstreamReader.sendResponse(packet);
248         }
249 
getNextMatchedPacket(Predicate<byte[]> filter)250         public byte[] getNextMatchedPacket(Predicate<byte[]> filter) throws Exception {
251             return mUpstreamReader.poll(PACKET_READ_TIMEOUT_MS, filter);
252         }
253 
verifyDownload(final TetheringTester dst, final ByteBuffer packet, final Predicate<byte[]> filter)254         public void verifyDownload(final TetheringTester dst, final ByteBuffer packet,
255                 final Predicate<byte[]> filter) throws Exception {
256             sendPacket(packet);
257             assertNotNull("Download fail", dst.getNextMatchedPacket(filter));
258         }
259     }
260 }
261