• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2025 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 com.android.server.net;
18 
19 import static com.android.server.net.HeaderCompressionUtils.compress6lowpan;
20 import static com.android.server.net.HeaderCompressionUtils.decompress6lowpan;
21 
22 import android.bluetooth.BluetoothSocket;
23 import android.os.Handler;
24 import android.os.ParcelFileDescriptor;
25 import android.system.ErrnoException;
26 import android.system.Os;
27 import android.system.OsConstants;
28 import android.util.Log;
29 
30 import com.android.internal.annotations.VisibleForTesting;
31 
32 import java.io.IOException;
33 import java.io.InputStream;
34 import java.io.OutputStream;
35 import java.nio.BufferUnderflowException;
36 
37 /**
38  * Forwards packets from a BluetoothSocket of type L2CAP to a tun fd and vice versa.
39  *
40  * The forwarding logic operates on raw IP packets and there are no ethernet headers.
41  * Therefore, L3 MTU = L2 MTU.
42  */
43 public class L2capPacketForwarder {
44     private static final String TAG = "L2capPacketForwarder";
45 
46     // DCT specifies an MTU of 1500.
47     // TODO: Set /proc/sys/net/ipv6/conf/${iface}/mtu to 1280 and the link MTU to 1528 to accept
48     // slightly larger packets on ingress (i.e. packets passing through a NAT64 gateway).
49     // MTU determines the value of the read buffers, so use the larger of the two.
50     @VisibleForTesting
51     public static final int MTU = 1528;
52     private final Handler mHandler;
53     private final IReadWriteFd mTunFd;
54     private final IReadWriteFd mL2capFd;
55     private final L2capThread mIngressThread;
56     private final L2capThread mEgressThread;
57     private final ICallback mCallback;
58 
59     public interface ICallback {
60         /** Called when an error is encountered; should tear down forwarding. */
onError()61         void onError();
62     }
63 
64     private interface IReadWriteFd {
65         /**
66          * Read up to len bytes into bytes[off] and return bytes actually read.
67          *
68          * bytes[] must be of size >= off + len.
69          */
read(byte[] bytes, int off, int len)70         int read(byte[] bytes, int off, int len) throws IOException;
71         /**
72          * Write len bytes starting from bytes[off]
73          *
74          * bytes[] must be of size >= off + len.
75          */
write(byte[] bytes, int off, int len)76         void write(byte[] bytes, int off, int len) throws IOException;
77         /** Disallow further receptions, shutdown(fd, SHUT_RD) */
shutdownRead()78         void shutdownRead();
79         /** Disallow further transmissions, shutdown(fd, SHUT_WR) */
shutdownWrite()80         void shutdownWrite();
81         /** Close the fd */
close()82         void close();
83     }
84 
85     @VisibleForTesting
86     public static class BluetoothSocketWrapper implements IReadWriteFd {
87         private final BluetoothSocket mSocket;
88         private final InputStream mInputStream;
89         private final OutputStream mOutputStream;
90 
BluetoothSocketWrapper(BluetoothSocket socket)91         public BluetoothSocketWrapper(BluetoothSocket socket) {
92             // TODO: assert that MTU can fit within Bluetooth L2CAP SDU (maximum size of an L2CAP
93             // packet). The L2CAP SDU is 65535 by default, but can be less when using hardware
94             // offload.
95             mSocket = socket;
96             try {
97                 mInputStream = socket.getInputStream();
98                 mOutputStream = socket.getOutputStream();
99             } catch (IOException e) {
100                 // Per the API docs, this should not actually be possible.
101                 Log.wtf(TAG, "Failed to get Input/OutputStream", e);
102                 // Fail hard.
103                 throw new IllegalStateException("Failed to get Input/OutputStream");
104             }
105         }
106 
107         /** Read from the BluetoothSocket. */
108         @Override
read(byte[] bytes, int off, int len)109         public int read(byte[] bytes, int off, int len) throws IOException {
110             // Note: EINTR is handled internally and automatically triggers a retry loop.
111             int bytesRead = mInputStream.read(bytes, off, len);
112             if (bytesRead < 0 || bytesRead > MTU) {
113                 // Don't try to recover, just trigger network teardown. This might indicate a bug in
114                 // the Bluetooth stack.
115                 throw new IOException("Packet exceeds MTU or reached EOF. Read: " + bytesRead);
116             }
117             return bytesRead;
118         }
119 
120         /** Write to the BluetoothSocket. */
121         @Override
write(byte[] bytes, int off, int len)122         public void write(byte[] bytes, int off, int len) throws IOException {
123             // Note: EINTR is handled internally and automatically triggers a retry loop.
124             mOutputStream.write(bytes, off, len);
125         }
126 
127         @Override
shutdownRead()128         public void shutdownRead() {
129             // BluetoothSocket does not expose methods to shutdown read / write individually;
130             // however, BluetoothSocket#close() shuts down both read and write and is safe to be
131             // called multiple times from any thread.
132             try {
133                 mSocket.close();
134             } catch (IOException e) {
135                 Log.w(TAG, "shutdownRead: Failed to close BluetoothSocket", e);
136             }
137         }
138 
139         @Override
shutdownWrite()140         public void shutdownWrite() {
141             // BluetoothSocket does not expose methods to shutdown read / write individually;
142             // however, BluetoothSocket#close() shuts down both read and write and is safe to be
143             // called multiple times from any thread.
144             try {
145                 mSocket.close();
146             } catch (IOException e) {
147                 Log.w(TAG, "shutdownWrite: Failed to close BluetoothSocket", e);
148             }
149         }
150 
151         @Override
close()152         public void close() {
153             // BluetoothSocket#close() is safe to be called multiple times.
154             try {
155                 mSocket.close();
156             } catch (IOException e) {
157                 Log.w(TAG, "close: Failed to close BluetoothSocket", e);
158             }
159         }
160     }
161 
162     @VisibleForTesting
163     public static class FdWrapper implements IReadWriteFd {
164         private final ParcelFileDescriptor mFd;
165 
FdWrapper(ParcelFileDescriptor fd)166         public FdWrapper(ParcelFileDescriptor fd) {
167             mFd = fd;
168         }
169 
170         @Override
read(byte[] bytes, int off, int len)171         public int read(byte[] bytes, int off, int len) throws IOException {
172             try {
173                 // Note: EINTR is handled internally and automatically triggers a retry loop.
174                 return Os.read(mFd.getFileDescriptor(), bytes, off, len);
175             } catch (ErrnoException e) {
176                 throw new IOException(e);
177             }
178         }
179 
180         /**
181          * Write to BluetoothSocket.
182          */
183         @Override
write(byte[] bytes, int off, int len)184         public void write(byte[] bytes, int off, int len) throws IOException {
185             try {
186                 // Note: EINTR is handled internally and automatically triggers a retry loop.
187                 Os.write(mFd.getFileDescriptor(), bytes, off, len);
188             } catch (ErrnoException e) {
189                 throw new IOException(e);
190             }
191         }
192 
193         @Override
shutdownRead()194         public void shutdownRead() {
195             try {
196                 Os.shutdown(mFd.getFileDescriptor(), OsConstants.SHUT_RD);
197             } catch (ErrnoException e) {
198                 Log.w(TAG, "shutdownRead: Failed to shutdown(fd, SHUT_RD)", e);
199             }
200         }
201 
202         @Override
shutdownWrite()203         public void shutdownWrite() {
204             try {
205                 Os.shutdown(mFd.getFileDescriptor(), OsConstants.SHUT_WR);
206             } catch (ErrnoException e) {
207                 Log.w(TAG, "shutdownWrite: Failed to shutdown(fd, SHUT_WR)", e);
208             }
209         }
210 
211         @Override
close()212         public void close() {
213             try {
214                 // Safe to call multiple times. Both Os.close(FileDescriptor) and
215                 // ParcelFileDescriptor#close() offer protection against double-closing an fd.
216                 mFd.close();
217             } catch (IOException e) {
218                 Log.w(TAG, "close: Failed to close fd", e);
219             }
220         }
221     }
222 
223     private class L2capThread extends Thread {
224         // Set mBuffer length to MTU + 1 to catch read() overflows.
225         private final byte[] mBuffer = new byte[MTU + 1];
226         private volatile boolean mIsRunning = true;
227 
228         private final String mLogTag;
229         private final IReadWriteFd mReadFd;
230         private final IReadWriteFd mWriteFd;
231         private final boolean mIsIngress;
232         private final boolean mCompressHeaders;
233 
L2capThread(IReadWriteFd readFd, IReadWriteFd writeFd, boolean isIngress, boolean compressHeaders)234         L2capThread(IReadWriteFd readFd, IReadWriteFd writeFd, boolean isIngress,
235                 boolean compressHeaders) {
236             super("L2capNetworkProvider-ForwarderThread");
237             mLogTag = isIngress ? "L2capForwarderThread-Ingress" : "L2capForwarderThread-Egress";
238             mReadFd = readFd;
239             mWriteFd = writeFd;
240             mIsIngress = isIngress;
241             mCompressHeaders = compressHeaders;
242         }
243 
postOnError()244         private void postOnError() {
245             mHandler.post(() -> {
246                 // All callbacks must be called on handler thread.
247                 mCallback.onError();
248             });
249         }
250 
251         @Override
run()252         public void run() {
253             while (mIsRunning) {
254                 try {
255                     int readBytes = mReadFd.read(mBuffer, 0 /*off*/, mBuffer.length);
256                     // No bytes to write, continue.
257                     if (readBytes <= 0) {
258                         Log.w(mLogTag, "Zero-byte read encountered: " + readBytes);
259                         continue;
260                     }
261 
262                     if (mCompressHeaders) {
263                         if (mIsIngress) {
264                             readBytes = decompress6lowpan(mBuffer, readBytes);
265                         } else {
266                             readBytes = compress6lowpan(mBuffer, readBytes);
267                         }
268                     }
269 
270                     // If the packet is 0-length post de/compression or exceeds MTU, drop it.
271                     // Note that a large read on BluetoothSocket throws an IOException to tear down
272                     // the network.
273                     if (readBytes <= 0 || readBytes > MTU) continue;
274 
275                     mWriteFd.write(mBuffer, 0 /*off*/, readBytes);
276                 } catch (IOException|BufferUnderflowException e) {
277                     Log.e(mLogTag, "L2capThread exception", e);
278                     // Tear down the network on any error.
279                     mIsRunning = false;
280                     // Notify provider that forwarding has stopped.
281                     postOnError();
282                 }
283             }
284         }
285 
tearDown()286         public void tearDown() {
287             mIsRunning = false;
288             mReadFd.shutdownRead();
289             mWriteFd.shutdownWrite();
290         }
291     }
292 
L2capPacketForwarder(Handler handler, ParcelFileDescriptor tunFd, BluetoothSocket socket, boolean compressHdrs, ICallback cb)293     public L2capPacketForwarder(Handler handler, ParcelFileDescriptor tunFd, BluetoothSocket socket,
294             boolean compressHdrs, ICallback cb) {
295         this(handler, new FdWrapper(tunFd), new BluetoothSocketWrapper(socket), compressHdrs, cb);
296     }
297 
298     @VisibleForTesting
L2capPacketForwarder(Handler handler, IReadWriteFd tunFd, IReadWriteFd l2capFd, boolean compressHeaders, ICallback cb)299     L2capPacketForwarder(Handler handler, IReadWriteFd tunFd, IReadWriteFd l2capFd,
300             boolean compressHeaders, ICallback cb) {
301         mHandler = handler;
302         mTunFd = tunFd;
303         mL2capFd = l2capFd;
304         mCallback = cb;
305 
306         mIngressThread = new L2capThread(l2capFd, tunFd, true /*isIngress*/, compressHeaders);
307         mEgressThread = new L2capThread(tunFd, l2capFd, false /*isIngress*/, compressHeaders);
308 
309         mIngressThread.start();
310         mEgressThread.start();
311     }
312 
313     /**
314      * Tear down the L2capPacketForwarder.
315      *
316      * This operation closes both the passed tun fd and BluetoothSocket.
317      **/
tearDown()318     public void tearDown() {
319         // Destroying both threads first guarantees that both read and write side of FD have been
320         // shutdown.
321         mIngressThread.tearDown();
322         mEgressThread.tearDown();
323 
324         // In order to interrupt a blocking read on the BluetoothSocket, the BluetoothSocket must be
325         // closed (which triggers shutdown()). This means, the BluetoothSocket must be closed inside
326         // L2capPacketForwarder. Tear down the tun fd alongside it for consistency.
327         mTunFd.close();
328         mL2capFd.close();
329 
330         try {
331             mIngressThread.join();
332         } catch (InterruptedException e) {
333             // join() interrupted in tearDown path, do nothing.
334         }
335         try {
336             mEgressThread.join();
337         } catch (InterruptedException e) {
338             // join() interrupted in tearDown path, do nothing.
339         }
340     }
341 }
342