1 /*
2  * Copyright (C) 2020 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.ip;
18 
19 import static android.system.OsConstants.IPPROTO_ICMPV6;
20 
21 import static com.android.net.module.util.IpUtils.icmpv6Checksum;
22 import static com.android.net.module.util.NetworkStackConstants.ETHER_SRC_ADDR_OFFSET;
23 import static com.android.networkstack.tethering.util.TetheringUtils.getTetheringJniLibraryName;
24 
25 import static org.junit.Assert.assertEquals;
26 import static org.junit.Assert.assertNotNull;
27 
28 import android.app.Instrumentation;
29 import android.content.Context;
30 import android.net.INetd;
31 import android.net.InetAddresses;
32 import android.net.MacAddress;
33 import android.os.Build;
34 import android.os.Handler;
35 import android.os.HandlerThread;
36 import android.os.IBinder;
37 import android.os.Looper;
38 
39 import androidx.test.InstrumentationRegistry;
40 import androidx.test.filters.SmallTest;
41 
42 import com.android.net.module.util.InterfaceParams;
43 import com.android.networkstack.tethering.util.TetheringUtils;
44 import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
45 import com.android.testutils.DevSdkIgnoreRunner;
46 import com.android.testutils.TapPacketReader;
47 import com.android.testutils.TapPacketReaderRule;
48 
49 import org.junit.After;
50 import org.junit.Before;
51 import org.junit.BeforeClass;
52 import org.junit.Rule;
53 import org.junit.Test;
54 import org.junit.runner.RunWith;
55 import org.mockito.MockitoAnnotations;
56 
57 import java.io.IOException;
58 import java.nio.ByteBuffer;
59 
60 @RunWith(DevSdkIgnoreRunner.class)
61 @IgnoreUpTo(Build.VERSION_CODES.R)
62 @SmallTest
63 public class DadProxyTest {
64     private static final int DATA_BUFFER_LEN = 4096;
65     private static final int PACKET_TIMEOUT_MS = 2_000;  // Long enough for DAD to succeed.
66 
67     // Start the readers manually on a common handler shared with DadProxy, for simplicity
68     @Rule
69     public final TapPacketReaderRule mUpstreamReader = new TapPacketReaderRule(
70             DATA_BUFFER_LEN, false /* autoStart */);
71     @Rule
72     public final TapPacketReaderRule mTetheredReader = new TapPacketReaderRule(
73             DATA_BUFFER_LEN, false /* autoStart */);
74 
75     private InterfaceParams mUpstreamParams, mTetheredParams;
76     private HandlerThread mHandlerThread;
77     private Handler mHandler;
78     private TapPacketReader mUpstreamPacketReader, mTetheredPacketReader;
79 
80     private static INetd sNetd;
81 
82     @BeforeClass
setupOnce()83     public static void setupOnce() {
84         System.loadLibrary(getTetheringJniLibraryName());
85 
86         final Instrumentation inst = InstrumentationRegistry.getInstrumentation();
87         final IBinder netdIBinder =
88                 (IBinder) inst.getContext().getSystemService(Context.NETD_SERVICE);
89         sNetd = INetd.Stub.asInterface(netdIBinder);
90     }
91 
92     @Before
setUp()93     public void setUp() throws Exception {
94         MockitoAnnotations.initMocks(this);
95 
96         mHandlerThread = new HandlerThread(getClass().getSimpleName());
97         mHandlerThread.start();
98         mHandler = new Handler(mHandlerThread.getLooper());
99 
100         setupTapInterfaces();
101 
102         // Looper must be prepared here since AndroidJUnitRunner runs tests on separate threads.
103         if (Looper.myLooper() == null) Looper.prepare();
104 
105         DadProxy mProxy = setupProxy();
106     }
107 
108     @After
tearDown()109     public void tearDown() throws Exception {
110         mUpstreamReader.stop();
111         mTetheredReader.stop();
112 
113         if (mHandlerThread != null) {
114             mHandlerThread.quitSafely();
115             mHandlerThread.join(PACKET_TIMEOUT_MS);
116         }
117 
118         if (mTetheredParams != null) {
119             sNetd.networkRemoveInterface(INetd.LOCAL_NET_ID, mTetheredParams.name);
120         }
121         if (mUpstreamParams != null) {
122             sNetd.networkRemoveInterface(INetd.LOCAL_NET_ID, mUpstreamParams.name);
123         }
124     }
125 
setupTapInterfaces()126     private void setupTapInterfaces() throws Exception {
127         // Create upstream test iface.
128         mUpstreamReader.start(mHandler);
129         final String upstreamIface = mUpstreamReader.iface.getInterfaceName();
130         mUpstreamParams = InterfaceParams.getByName(upstreamIface);
131         assertNotNull(mUpstreamParams);
132         mUpstreamPacketReader = mUpstreamReader.getReader();
133 
134         // Create tethered test iface.
135         mTetheredReader.start(mHandler);
136         final String tetheredIface = mTetheredReader.getIface().getInterfaceName();
137         mTetheredParams = InterfaceParams.getByName(tetheredIface);
138         assertNotNull(mTetheredParams);
139         mTetheredPacketReader = mTetheredReader.getReader();
140     }
141 
142     private static final int IPV6_HEADER_LEN = 40;
143     private static final int ETH_HEADER_LEN = 14;
144     private static final int ICMPV6_NA_NS_LEN = 24;
145     private static final int LL_TARGET_OPTION_LEN = 8;
146     private static final int ICMPV6_CHECKSUM_OFFSET = 2;
147     private static final int ETHER_TYPE_IPV6 = 0x86dd;
148 
createDadPacket(int type)149     private static ByteBuffer createDadPacket(int type) {
150         // Refer to buildArpPacket()
151         int icmpLen = ICMPV6_NA_NS_LEN
152                 + (type == NeighborPacketForwarder.ICMPV6_NEIGHBOR_ADVERTISEMENT
153                 ? LL_TARGET_OPTION_LEN : 0);
154         final ByteBuffer buf = ByteBuffer.allocate(icmpLen + IPV6_HEADER_LEN + ETH_HEADER_LEN);
155 
156         // Ethernet header.
157         final MacAddress srcMac = MacAddress.fromString("33:33:ff:66:77:88");
158         buf.put(srcMac.toByteArray());
159         final MacAddress dstMac = MacAddress.fromString("01:02:03:04:05:06");
160         buf.put(dstMac.toByteArray());
161         buf.putShort((short) ETHER_TYPE_IPV6);
162 
163         // IPv6 header
164         byte[] version = {(byte) 0x60, 0x00, 0x00, 0x00};
165         buf.put(version);                                           // Version
166         buf.putShort((byte) icmpLen);                               // Length
167         buf.put((byte) IPPROTO_ICMPV6);                             // Next header
168         buf.put((byte) 0xff);                                       // Hop limit
169 
170         final byte[] target =
171             InetAddresses.parseNumericAddress("fe80::1122:3344:5566:7788").getAddress();
172         final byte[] src;
173         final byte[] dst;
174         if (type == NeighborPacketForwarder.ICMPV6_NEIGHBOR_SOLICITATION) {
175             src = InetAddresses.parseNumericAddress("::").getAddress();
176             dst = InetAddresses.parseNumericAddress("ff02::1:ff66:7788").getAddress();
177         } else {
178             src = target;
179             dst = TetheringUtils.ALL_NODES;
180         }
181         buf.put(src);
182         buf.put(dst);
183 
184         // ICMPv6 Header
185         buf.put((byte) type);                                       // Type
186         buf.put((byte) 0x00);                                       // Code
187         buf.putShort((short) 0);                                    // Checksum
188         buf.putInt(0);                                              // Reserved
189         buf.put(target);
190 
191         if (type == NeighborPacketForwarder.ICMPV6_NEIGHBOR_ADVERTISEMENT) {
192             //NA packet has LL target address
193             //ICMPv6 Option
194             buf.put((byte) 0x02);                                   // Type
195             buf.put((byte) 0x01);                                   // Length
196             byte[] ll_target = MacAddress.fromString("01:02:03:04:05:06").toByteArray();
197             buf.put(ll_target);
198         }
199 
200         // Populate checksum field
201         final int transportOffset = ETH_HEADER_LEN + IPV6_HEADER_LEN;
202         final short checksum = icmpv6Checksum(buf, ETH_HEADER_LEN, transportOffset, icmpLen);
203         buf.putShort(transportOffset + ICMPV6_CHECKSUM_OFFSET, checksum);
204 
205         buf.flip();
206         return buf;
207     }
208 
setupProxy()209     private DadProxy setupProxy() throws Exception {
210         DadProxy proxy = new DadProxy(mHandler, mTetheredParams);
211         mHandler.post(() -> proxy.setUpstreamIface(mUpstreamParams));
212 
213         // Upstream iface is added to local network to simplify test case.
214         // Otherwise the test needs to create and destroy a network for the upstream iface.
215         sNetd.networkAddInterface(INetd.LOCAL_NET_ID, mUpstreamParams.name);
216         sNetd.networkAddInterface(INetd.LOCAL_NET_ID, mTetheredParams.name);
217 
218         return proxy;
219     }
220 
221     // TODO: change to assert.
waitForPacket(ByteBuffer packet, TapPacketReader reader)222     private boolean waitForPacket(ByteBuffer packet, TapPacketReader reader) {
223         byte[] p;
224 
225         while ((p = reader.popPacket(PACKET_TIMEOUT_MS)) != null) {
226             final ByteBuffer buffer = ByteBuffer.wrap(p);
227 
228             if (buffer.compareTo(packet) == 0) return true;
229         }
230         return false;
231     }
232 
copy(ByteBuffer buf)233     private ByteBuffer copy(ByteBuffer buf) {
234         // There does not seem to be a way to copy ByteBuffers. ByteBuffer does not implement
235         // clone() and duplicate() copies the metadata but shares the contents.
236         return ByteBuffer.wrap(buf.array().clone());
237     }
238 
updateDstMac(ByteBuffer buf, MacAddress mac)239     private void updateDstMac(ByteBuffer buf, MacAddress mac) {
240         buf.put(mac.toByteArray());
241         buf.rewind();
242     }
updateSrcMac(ByteBuffer buf, InterfaceParams ifaceParams)243     private void updateSrcMac(ByteBuffer buf, InterfaceParams ifaceParams) {
244         buf.position(ETHER_SRC_ADDR_OFFSET);
245         buf.put(ifaceParams.macAddr.toByteArray());
246         buf.rewind();
247     }
248 
receivePacketAndMaybeExpectForwarded(boolean expectForwarded, ByteBuffer in, TapPacketReader inReader, ByteBuffer out, TapPacketReader outReader)249     private void receivePacketAndMaybeExpectForwarded(boolean expectForwarded,
250             ByteBuffer in, TapPacketReader inReader, ByteBuffer out, TapPacketReader outReader)
251             throws IOException {
252 
253         inReader.sendResponse(in);
254         if (waitForPacket(out, outReader)) return;
255 
256         // When the test runs, DAD may be in progress, because the interface has just been created.
257         // If so, the DAD proxy will get EADDRNOTAVAIL when trying to send packets. It is not
258         // possible to work around this using IPV6_FREEBIND or IPV6_TRANSPARENT options because the
259         // kernel rawv6 code doesn't consider those options either when binding or when sending, and
260         // doesn't get the source address from the packet even in IPPROTO_RAW/HDRINCL mode (it only
261         // gets it from the socket or from cmsg).
262         //
263         // If DAD was in progress when the above was attempted, try again and expect the packet to
264         // be forwarded. Don't disable DAD in the test because if we did, the test would not notice
265         // if, for example, the DAD proxy code just crashed if it received EADDRNOTAVAIL.
266         final String msg = expectForwarded
267                 ? "Did not receive expected packet even after waiting for DAD:"
268                 : "Unexpectedly received packet:";
269 
270         inReader.sendResponse(in);
271         assertEquals(msg, expectForwarded, waitForPacket(out, outReader));
272     }
273 
receivePacketAndExpectForwarded(ByteBuffer in, TapPacketReader inReader, ByteBuffer out, TapPacketReader outReader)274     private void receivePacketAndExpectForwarded(ByteBuffer in, TapPacketReader inReader,
275             ByteBuffer out, TapPacketReader outReader) throws IOException {
276         receivePacketAndMaybeExpectForwarded(true, in, inReader, out, outReader);
277     }
278 
receivePacketAndExpectNotForwarded(ByteBuffer in, TapPacketReader inReader, ByteBuffer out, TapPacketReader outReader)279     private void receivePacketAndExpectNotForwarded(ByteBuffer in, TapPacketReader inReader,
280             ByteBuffer out, TapPacketReader outReader) throws IOException {
281         receivePacketAndMaybeExpectForwarded(false, in, inReader, out, outReader);
282     }
283 
284     @Test
testNaForwardingFromUpstreamToTether()285     public void testNaForwardingFromUpstreamToTether() throws Exception {
286         ByteBuffer na = createDadPacket(NeighborPacketForwarder.ICMPV6_NEIGHBOR_ADVERTISEMENT);
287 
288         ByteBuffer out = copy(na);
289         updateDstMac(out, MacAddress.fromString("33:33:00:00:00:01"));
290         updateSrcMac(out, mTetheredParams);
291 
292         receivePacketAndExpectForwarded(na, mUpstreamPacketReader, out, mTetheredPacketReader);
293     }
294 
295     @Test
296     // TODO: remove test once DAD works in both directions.
testNaForwardingFromTetherToUpstream()297     public void testNaForwardingFromTetherToUpstream() throws Exception {
298         ByteBuffer na = createDadPacket(NeighborPacketForwarder.ICMPV6_NEIGHBOR_ADVERTISEMENT);
299 
300         ByteBuffer out = copy(na);
301         updateDstMac(out, MacAddress.fromString("33:33:00:00:00:01"));
302         updateSrcMac(out, mTetheredParams);
303 
304         receivePacketAndExpectNotForwarded(na, mTetheredPacketReader, out, mUpstreamPacketReader);
305     }
306 
307     @Test
testNsForwardingFromTetherToUpstream()308     public void testNsForwardingFromTetherToUpstream() throws Exception {
309         ByteBuffer ns = createDadPacket(NeighborPacketForwarder.ICMPV6_NEIGHBOR_SOLICITATION);
310 
311         ByteBuffer out = copy(ns);
312         updateSrcMac(out, mUpstreamParams);
313 
314         receivePacketAndExpectForwarded(ns, mTetheredPacketReader, out, mUpstreamPacketReader);
315     }
316 
317     @Test
318     // TODO: remove test once DAD works in both directions.
testNsForwardingFromUpstreamToTether()319     public void testNsForwardingFromUpstreamToTether() throws Exception {
320         ByteBuffer ns = createDadPacket(NeighborPacketForwarder.ICMPV6_NEIGHBOR_SOLICITATION);
321 
322         ByteBuffer out = copy(ns);
323         updateSrcMac(ns, mUpstreamParams);
324 
325         receivePacketAndExpectNotForwarded(ns, mUpstreamPacketReader, out, mTetheredPacketReader);
326     }
327 }
328