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